Rivet.psm1
$Connection = New-Object Data.SqlClient.SqlConnection $RivetSchemaName = 'rivet' $RivetMigrationsTableName = 'Migrations' $RivetMigrationsTableFullName = "[$($RivetSchemaName)].[$($RivetMigrationsTableName)]" $RivetActivityTableName = 'Activity' $rivetModuleRoot = $PSScriptRoot $timer = New-Object 'Diagnostics.Stopwatch' $timerForWrites = New-Object 'Diagnostics.Stopwatch' $timingLevel = 0 $plugins = @() function Write-Timing { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Message, [Switch] $Indent, [Switch] $Outdent ) Set-StrictMode -Version 'Latest' if( -not $timer.IsRunning ) { $timer.Start() } if( -not $timerForWrites.IsRunning ) { $timerForWrites.Start() } if( $Outdent ) { $script:timingLevel -= 1 } $prefix = ' ' * ($timingLevel * 2) function ConvertTo-DurationString { param( [Parameter(Mandatory,ValueFromPipeline)] [TimeSpan]$TimeSpan ) process { Set-StrictMode -Version 'Latest' $hours = '' if( $TimeSpan.Hours ) { $hours = "$($TimeSpan.Hours.ToString())h " } $minutes = '' if( $TimeSpan.Minutes ) { $minutes = "$($TimeSpan.Minutes.ToString('00'))m " } $seconds = '' if( $TimeSpan.Seconds ) { $seconds = "$($TimeSpan.Seconds.ToString('00'))s " } return "$($hours)$($minutes)$($seconds)$($TimeSpan.Milliseconds.ToString('000'))ms" } } # $DebugPreference = 'Continue' if( $DebugPreference -eq 'Continue' ) { Write-Debug -Message ('{0,17} {1,17} {2}{3}' -f ($timer.Elapsed | ConvertTo-DurationString),($timerForWrites.Elapsed | ConvertTo-DurationString),$prefix,$Message) } $timerForWrites.Restart() if( $Indent ) { $script:timingLevel += 1 } if( $timingLevel -lt 0 ) { $timingLevel = 0 } } function Test-RivetTypeDataMember { [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory=$true)] [string] # The type name to check. $TypeName, [Parameter(Mandatory=$true)] [string] # The name of the member to check. $MemberName ) Set-StrictMode -Version 'Latest' $typeData = Get-TypeData -TypeName $TypeName if( -not $typeData ) { # The type isn't defined or there is no extended type data on it. return $false } return $typeData.Members.ContainsKey( $MemberName ) } $oldVersionLoadedMsg = 'You have an old version of Rivet loaded. Please restart your PowerShell session.' function New-RivetObject { param( [Parameter(Mandatory)] [String]$TypeName, [Object[]]$ArgumentList ) try { return (New-Object -TypeName $TypeName -ArgumentList $ArgumentList -ErrorAction Ignore) } catch { Write-Error -Message ('Unable to find type "{0}". {1}' -f $TypeName,$oldVersionLoadedMsg) -ErrorAction Stop } } if( -not (Test-RivetTypeDataMember -TypeName 'Rivet.OperationResult' -MemberName 'MigrationID') ) { Update-TypeData -TypeName 'Rivet.OperationResult' -MemberType ScriptProperty -MemberName 'MigrationID' -Value { $this.Migration.ID } } # Added in Rivet 0.10.0 Test-RivetTypeDataMember -TypeName 'Rivet.Scale' -MemberName 'Value' # Import functions on developer computers. & { Join-Path -Path $rivetModuleRoot -ChildPath 'Functions' Join-Path -Path $rivetModuleRoot -ChildPath 'Functions\Columns' Join-Path -Path $rivetModuleRoot -ChildPath 'Functions\Operations' } | Where-Object { Test-Path -Path $_ -PathType Container } | Get-ChildItem -Filter '*-*.ps1' | ForEach-Object { . $_.FullName } function Connect-Database { param( [Parameter(Mandatory=$true)] [string] # The SQL Server instance to connect to. $SqlServerName, [Parameter(Mandatory=$true)] [string] # The database to connect to. $Database, [UInt32] # The time (in seconds) to wait for a connection to open. The default is 10 seconds. $ConnectionTimeout = 10 ) Set-StrictMode -Version 'Latest' $startedAt = Get-Date if( -not $Connection -or $Connection.DataSource -ne $SqlServerName -or $Connection.State -eq [Data.ConnectionState]::Closed) { Disconnect-Database $connString = 'Server={0};Database=master;Integrated Security=True;Connection Timeout={1}' -f $SqlServerName,$ConnectionTimeout Set-Variable -Name 'Connection' -Scope 1 -Value (New-Object 'Data.SqlClient.SqlConnection' ($connString)) -Confirm:$False -WhatIf:$false try { $Connection.Open() } catch { $ex = $_.Exception while( $ex.InnerException ) { $ex = $ex.InnerException } Write-Error ('Failed to connect to SQL Server "{0}" (connection string: {1}). Does this database server exist? ({2})' -f $SqlServerName,$connString,$ex.Message) return $false } } if( -not ($Connection | Get-Member -Name 'Transaction' ) ) { $Connection | Add-Member -MemberType NoteProperty -Name 'Transaction' -Value $null } if( $Connection.Database -ne 'master' ) { $Connection.ChangeDatabase( 'master' ) } $query = 'select 1 from sys.databases where name=''{0}''' -f $Database $dbExists = Invoke-Query -Query $query -AsScalar if( -not $dbExists ) { Write-Debug -Message ('Creating database {0}.{1}.' -f $SqlServerName,$Database) $query = 'create database [{0}]' -f $Database Invoke-Query -Query $query -NonQuery } $Connection.ChangeDatabase( $Database ) Write-Debug -Message ('{0,8} (ms) Connect-Database' -f ([int]((Get-Date) - $startedAt).TotalMilliseconds)) return $true } function Convert-FileInfoToMigration { <# .SYNOPSIS Converts a `System.IO.FileInfo` object containing a migration into a `Rivet.Operations.Operation` object. #> [CmdletBinding()] [OutputType([Rivet.Migration])] param( [Parameter(Mandatory)] # The Rivet configuration to use. [Rivet.Configuration.Configuration]$Configuration, [Parameter(Mandatory,ValueFromPipeline)] # The database whose migrations to get. [IO.FileInfo]$InputObject ) begin { Set-StrictMode -Version 'Latest' Write-Timing -Message 'Convert-FileInfoToMigration BEGIN' -Indent function Clear-Migration { ('function:Push-Migration','function:Pop-Migration') | Where-Object { Test-Path -Path $_ } | Remove-Item -WhatIf:$false -Confirm:$false } Clear-Migration } process { filter Add-Operation { param( [Parameter(Mandatory,ValueFromPipeline)] # The migration object to invoke. [Object]$Operation, [Parameter(ParameterSetName='Push',Mandatory)] [AllowEmptyCollection()] [Collections.Generic.List[Rivet.Operations.Operation]]$OperationsList, [Parameter(ParameterSetName='Pop',Mandatory)] [switch]$Pop ) Set-StrictMode -Version 'Latest' foreach( $operationItem in $Operation ) { if( $operationItem -isnot [Rivet.Operations.Operation] ) { continue } $pluginParameter = @{ Migration = $m ; Operation = $_ } [Rivet.Operations.Operation[]]$operations = & { Invoke-RivetPlugin -Event ([Rivet.Events]::BeforeOperationLoad) -Parameter $pluginParameter $operationItem Invoke-RivetPlugin -Event ([Rivet.Events]::AfterOperationLoad) -Parameter $pluginParameter } | Where-Object { $_ -is [Rivet.Operations.Operation] } | Repair-Operation $OperationsList.AddRange($operations) } } foreach( $fileInfo in $InputObject ) { $dbName = Split-Path -Parent -Path $fileInfo.FullName $dbName = Split-Path -Parent -Path $dbName $dbName = Split-Path -Leaf -Path $dbName $m = New-Object 'Rivet.Migration' $fileInfo.MigrationID, $fileInfo.MigrationName, $fileInfo.FullName, $dbName Write-Timing -Message ('Convert-FileInfoToMigration {0}' -f $m.FullName) # Do not remove. It's a variable expected in some migrations. $DBMigrationsRoot = Split-Path -Parent -Path $fileInfo.FullName . $fileInfo.FullName | Out-Null try { if( -not (Test-Path -Path 'function:Push-Migration') ) { throw (@' Push-Migration function not found. All migrations are required to have a Push-Migration function that contains at least one operation. Here's some sample code to get you started: function Push-Migration { Add-Table 'LetsCreateATable' { int 'ID' -NotNull } } '@) } Push-Migration | Add-Operation -OperationsList $m.PushOperations if( $m.PushOperations.Count -eq 0 ) { throw (@' Push-Migration function is empty and contains no operations. Maybe you''d like to create a table? Here's some sample code to get you started: function Push-Migration { Add-Table 'LetsCreateATable' { int 'ID' -NotNull } } '@) } if( -not (Test-Path -Path 'function:Pop-Migration') ) { throw (@' Pop-Migration function not found. All migrations are required to have a Pop-Migration function that contains at least one operation. Here's some sample code to get you started: function Pop-Migration { Remove-Table 'LetsCreateATable' } '@) return } Pop-Migration | Add-Operation -OperationsList $m.PopOperations if( $m.PopOperations.Count -eq 0 ) { throw (@' Pop-Migration function is empty and contains no operations. Maybe you''d like to drop a table? Here's some sample code to get you started: function Pop-Migration { Remove-Table 'LetsCreateATable' } '@) } $m | Write-Output } catch { Write-RivetError -Message ('Loading migration "{0}" failed' -f $m.Path) ` -CategoryInfo $_.CategoryInfo.Category ` -ErrorID $_.FullyQualifiedErrorID ` -Exception $_.Exception ` -CallStack ($_.ScriptStackTrace) } finally { Clear-Migration } } } end { Write-Timing -Message 'Convert-FileInfoToMigration END' -Outdent } } function Disconnect-Database { param( ) if( $Connection -and $Connection.State -ne [Data.ConnectionState]::Closed ) { $Connection.Close() } } 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 } } function Export-Row { <# .SYNOPSIS Export rows from a database as a migration where those rows get added using the `Add-Row` operation. .DESCRIPTION When getting your database working with Rivet, you may want to get some data exported into an initial migration. This script does that. .EXAMPLE Export-Row -SqlServerName .\Rivet -DatabaseName 'Rivet' -SchemaName 'rivet' -TableName 'Migrations' -Column 'MigrationID','RunAtUtc' Demonstrates how to export the `MigrationID` and `RunAtUtc` columns of the `rivet.Migrations` table from the `.\Rivet.Rivet` database #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string] # The SQL Server to connect to. $SqlServerName, [Parameter(Mandatory=$true)] [string] # The name of the database. $DatabaseName, [string] # The schema of the table. $SchemaName = 'dbo', [Parameter(Mandatory=$true)] [string] # The name of the table. $TableName, [Parameter()] [string[]] # The columns to export. $Column, [string] # An orderBy clause to use to order the results. $OrderBy ) #Require -Version 3 Set-StrictMode -Version Latest $connectionString = 'Server={0};Database={1};Integrated Security=True;' -f $SqlServerName,$DatabaseName $connection = New-Object Data.SqlClient.SqlConnection $connectionString $columnClause = $Column -join ', ' $query = 'select {0} from {1}.{2}' -f $columnClause,$SchemaName,$TableName if( $OrderBy ) { $query += ' order by {0}' -f $OrderBy } $cmd = New-Object Data.SqlClient.SqlCommand ($query,$connection) $connection.Open() try { ' Add-Row -SchemaName ''{0}'' -TableName ''{1}'' -Column @(' $cmdReader = $cmd.ExecuteReader() try { if( -not $cmdReader.HasRows ) { return } while( $cmdReader.Read() ) { ' @{' for ($i= 0; $i -lt $cmdReader.FieldCount; $i++) { if( $cmdReader.IsDbNull( $i ) ) { continue } $name = $cmdReader.GetName( $i ) $value = $cmdReader.GetValue($i) if( $value -is [Boolean] ) { $value = if( $cmdReader.GetBoolean($i) ) { '1' } else { '0' } } elseif( $value -is [string] ) { $value = "'{0}'" -f $value.ToString().Replace("'","''") } elseif( $value -is [DAteTime] -or $value -is [Guid] ) { $value = "'{0}'" -f $value } else { $value = $value.ToString() } ' {0} = {1};' -f $name,$value } ' },' } } finally { ' )' $cmdReader.Close() } } finally { $cmd.Dispose() $connection.Close() } } function Get-Migration { <# .SYNOPSIS Gets the migrations for all or specific databases. .DESCRIPTION The `Get-Migration` function returns `Rivet.Migration` objects for all the migrations in all or specific databases. With no parameters, looks in the current directory for a `rivet.json` file and returns all the migrations for all the databases based on that configuration. Use the `ConfigFilePath` to load and use a specific `rivet.json` file. You can return migrations from specific databases by passing those database names as values to the `Database` parameter. The `Environment` parameter is used to load the correct environment-specific settings from the `rivet.json` file. You can filter what migrations are returned using the `Include` or `Exclude` parameters, which support wildcards, and will match any part of the migration's filename, including the ID. Use the `Before` and `After` parameters to return migrations whose timestamps/IDs come before and after the given dates. .OUTPUTS Rivet.Migration. .EXAMPLE Get-Migration Returns `Rivet.Migration` objects for each migration in each database. .EXAMPLE Get-Migration -Database StarWars Returns `Rivet.Migration` objects for each migration in the `StarWars` database. .EXAMPLE Get-Migration -Include 'CreateDeathStarTable','20150101000648','20150101150448_CreateRebelBaseTable','*Hoth*','20150707*' Demonstrates how to get use the `Include` parameter to find migrations by name, ID, or file name. In this case, the following migrations will be returned: * The migration whose name is `CreateDeathStarTable`. * The migration whose ID is `20150101000648`. * The migration whose full name is `20150101150448_CreateRebelBaseTable`. * Any migration whose contains `Hoth`. * Any migration created on July 7th, 2015. .EXAMPLE Get-Migration -Exclude 'CreateDeathStarTable','20150101000648','20150101150448_CreateRebelBaseTable','*Hoth*','20150707*' Demonstrates how to get use the `Exclude` parameter to skip/not return certain migrations by name, ID, or file name. In this case, the following migrations will be *not* be returned: * The migration whose name is `CreateDeathStarTable`. * The migration whose ID is `20150101000648`. * The migration whose full name is `20150101150448_CreateRebelBaseTable`. * Any migration whose contains `Hoth`. * Any migration created on July 7th, 2015. #> [CmdletBinding(DefaultParameterSetName='External')] [OutputType([Rivet.Migration])] param( [Parameter(ParameterSetName='External')] [string[]] # The database whose migrations to get.np $Database, [Parameter(ParameterSetName='External')] [string] # The environment settings to use. $Environment, [Parameter(ParameterSetName='External')] [string] # The path to the rivet.json file to use. Defaults to `rivet.json` in the current directory. $ConfigFilePath, [string[]] # A list of migrations to include. Matches against the migration's ID or Name or the migration's file name (without extension). Wildcards permitted. $Include, [string[]] # A list of migrations to exclude. Matches against the migration's ID or Name or the migration's file name (without extension). Wildcards permitted. $Exclude, [DateTime] # Only get migrations before this date. Default is all. $Before, [DateTime] # Only get migrations after this date. Default is all. $After ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Write-Timing -Message 'Get-Migration BEGIN' -Indent function Clear-Migration { ('function:Push-Migration','function:Pop-Migration') | Where-Object { Test-Path -Path $_ } | Remove-Item -WhatIf:$false -Confirm:$false } Clear-Migration Write-Timing -Message 'Get-Migration Clear-Migration' $getRivetConfigParams = @{ } if( $Database ) { $getRivetConfigParams['Database'] = $Database } if( $ConfigFilePath ) { $getRivetConfigParams['Path'] = $ConfigFilePath } if( $Environment ) { $getRivetConfigParams['Environment'] = $Environment } $Configuration = Get-RivetConfig @getRivetConfigParams if( -not $Configuration ) { return } Import-RivetPlugin -Path $Configuration.PluginPaths -ModuleName $Configuration.PluginModules Write-Timing -Message 'Get-Migration Import-RivetPlugin' $getMigrationFileParams = @{} @( 'Include', 'Exclude' ) | ForEach-Object { if( $PSBoundParameters.ContainsKey($_) ) { $getMigrationFileParams[$_] = $PSBoundParameters[$_] } } Get-MigrationFile -Configuration $Configuration @getMigrationFileParams | Where-Object { if( $PSBoundParameters.ContainsKey( 'Before' ) ) { $beforeTimestamp = [uint64]$Before.ToString('yyyyMMddHHmmss') if( $_.MigrationID -gt $beforeTimestamp ) { return $false } } if( $PSBoundParameters.ContainsKey( 'After' ) ) { $afterTimestamp = [uint64]$After.ToString('yyyyMMddHHmmss') if( $_.MigrationID -lt $afterTimestamp ) { return $false } } return $true } | Convert-FileInfoToMigration -Configuration $Configuration Write-Timing -Message 'Get-Migration END' -Outdent } function Get-MigrationFile { <# .SYNOPSIS Gets the migration script files. #> [CmdletBinding(DefaultParameterSetName='External')] [OutputType([IO.FileInfo])] param( [Parameter(Mandatory)] # The configuration to use. [Rivet.Configuration.Configuration]$Configuration, [Parameter(Mandatory,ParameterSetName='ByPath')] # The path to a migrations directory to get. [String[]]$Path, # A list of migrations to include. Matches against the migration's ID or Name or the migration's file name (without extension). Wildcards permitted. [String[]]$Include, # A list of migrations to exclude. Matches against the migration's ID or Name or the migration's file name (without extension). Wildcards permitted. [String[]]$Exclude ) Set-StrictMode -Version Latest Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Write-Timing -Message 'Get-MigrationFile BEGIN' -Indent $requiredMatches = @{ } if( $PSBoundParameters.ContainsKey('Include') ) { foreach( $includeItem in $Include ) { if( -not [Management.Automation.WildcardPattern]::ContainsWildcardCharacters($includeItem) ) { $requiredMatches[$includeItem] = $true } } } $foundMatches = @{ } Invoke-Command -ScriptBlock { if( $PSCmdlet.ParameterSetName -eq 'ByPath' ) { $Path } else { $Configuration.Databases | Select-Object -ExpandProperty 'MigrationsRoot' } } | ForEach-Object { Write-Debug -Message $_ if( (Test-Path -Path $_ -PathType Container) ) { Get-ChildItem -Path $_ -Filter '*_*.ps1' } elseif( (Test-Path -Path $_ -PathType Leaf) ) { Get-Item -Path $_ } } | ForEach-Object { if( $_.BaseName -notmatch '^(\d{14})_(.+)' ) { Write-Error ('Migration {0} has invalid name. Must be of the form `YYYYmmddhhMMss_MigrationName.ps1' -f $_.FullName) return } $id = [int64]$matches[1] $name = $matches[2] $_ | Add-Member -MemberType NoteProperty -Name 'MigrationID' -Value $id -PassThru | Add-Member -MemberType NoteProperty -Name 'MigrationName' -Value $name -PassThru } | Where-Object { if( -not ($PSBoundParameters.ContainsKey( 'Include' )) ) { return $true } $migration = $_ foreach( $includeItem in $Include ) { $foundMatch = $migration.MigrationID -like $includeItem -or ` $migration.MigrationName -like $includeItem -or ` $migration.BaseName -like $includeItem if( $foundMatch ) { $foundMatches[$includeItem] = $true return $true } } return $false } | Where-Object { if( -not ($PSBoundParameters.ContainsKey( 'Exclude' )) ) { return $true } $migration = $_ foreach( $pattern in $Exclude ) { $foundMatch = $migration.MigrationID -like $pattern -or ` $migration.MigrationName -like $pattern -or ` $migration.BaseName -like $pattern if( $foundMatch ) { return $false } } return $true } foreach( $requiredMatch in $requiredMatches.Keys ) { if( -not $foundMatches.ContainsKey( $requiredMatch ) ) { Write-Error ('Migration "{0}" not found.' -f $requiredMatch) } } Write-Timing -Message 'Get-MigrationFile BEGIN' -Outdent } function Get-RivetConfig { <# .SYNOPSIS Gets the configuration to use when running Rivet. .DESCRIPTION Rivet will look in the current directory for a `rivet.json` file. .LINK about_Rivet_Configuration .EXAMPLE Get-RivetConfig Looks in the current directory for a `rivet.json` file, loads it, and returns an object representing its configuration. .EXAMPLE Get-RivetConfig -Path F:\etc\rivet Demonstrates how to load a custom Rivet configuration file. #> [CmdletBinding()] [OutputType([Rivet.Configuration.Configuration])] param( # The list of specific database names being operated on. [String[]]$Database, # The name of the environment whose settings to return. If not provided, uses the default settings. [String]$Environment, # The path to the Rivet configuration file to load. Defaults to `rivet.json` in the current directory. [String]$Path ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState function Resolve-RivetConfigPath { [CmdletBinding()] param( [Parameter(Mandatory,ValueFromPipeline)] # The path from the rivet config file to resolve. [String]$ConfigPath, # The path *must* exist, so resolve it. [switch]$Resolve ) process { $originalPath = $ConfigPath if( -not [IO.Path]::IsPathRooted( $ConfigPath ) ) { $ConfigPath = Join-Path -Path $configRoot -ChildPath $ConfigPath } if( $Resolve ) { $resolvedPath = Resolve-Path -Path $ConfigPath | Select-Object -ExpandProperty 'Path' if( ($resolvedPath | Measure-Object).Count -gt 1 ) { Write-ValidationError -Message ('path "{0}" resolves to multiple items: "{1}". Please update the path so that it resolves to only one item, or remove items so that only one remains.' -f $originalPath,($resolvedPath -join '", "')) return } return $resolvedPath } else { return [IO.Path]::GetFullPath( $ConfigPath ) } } } $currentPropertyName = $null filter Get-ConfigProperty { [CmdletBinding()] param( [Parameter(Mandatory)] # The name of the property to get. [String]$Name, # The configuration value is required. [switch]$Required, [Parameter(Mandatory,ParameterSetName='AsInt')] # Set the configuration value as an integer. [switch]$AsInt, [Parameter(Mandatory,ParameterSetName='AsArray')] # Set the configuration value as a list of strings. [switch]$AsArray, [Parameter(Mandatory,ParameterSetName='AsPath')] # Set the configuration value as a path. [switch]$AsPath, [Parameter(ParameterSetName='AsPath')] # Resolves the path to an actual path. [switch]$Resolve, [Parameter(Mandatory,ParameterSetName='AsString')] # Set the configuration value as a string. [switch]$AsString, [Parameter(Mandatory,ParameterSetName='AsHashtable')] # Set the configuration value as a hashtable. [switch]$AsHashtable ) $value = $null if( $rawConfig | Get-Member -Name $Name ) { $value = $rawConfig.$Name } $env = Get-Environment if( $env -and ($env | Get-Member -Name $Name)) { $value = $env.$Name } if( -not $value ) { if( $Required ) { Write-ValidationError ('is required.') } return } switch ($PSCmdlet.ParameterSetName ) { 'AsInt' { if( -not ($value -is [int] -or $value -is [int64]) ) { Write-ValidationError -Message ('is invalid. It should be an integer but we found a "{0}".' -f $value.GetType().FullName) return } return $value } 'AsArray' { return [String[]]$value } 'AsPath' { $configPath = $value | Resolve-RivetConfigPath -Resolve:$Resolve if( -not $configPath ) { return } if( -not (Test-Path -Path $configPath) ) { Write-ValidationError ('path "{0}" not found.' -f $configPath) return } return $configPath } 'AsString' { return $value } 'AsHashtable' { $hashtable = @{ } Get-Member -InputObject $value -MemberType NoteProperty | ForEach-Object { $hashtable[$_.Name] = $value.($_.Name) } return ,$hashtable } } } function Write-ValidationError { param( [Parameter(Mandatory,Position=1)] # The error message to write. [String]$Message ) $envMsg = '' if( $Environment ) { $envMsg = 'environment "{0}": ' -f $Environment } $nameMsg = '' if( $currentPropertyName ) { $nameMsg = 'property "{0}": ' -f $currentPropertyName } Write-Error -Message ('Invalid Rivet configuration file "{0}": {1}{2}{3} See about_Rivet_Configuration for more information.' -f $Path,$envMsg,$nameMsg,$Message) } function Get-Environment { if( $Environment ) { if( ($rawConfig | Get-Member -Name 'Environments') -and ($rawConfig.Environments | Get-Member -Name $Environment) ) { $rawConfig.Environments.$Environment } } } ## If there is no $Path defined set $Path to current directory if( -not $Path ) { $Path = Get-Location | Select-Object -ExpandProperty 'ProviderPath' $Path = Join-Path -Path $Path -ChildPath 'rivet.json' } if( -not [IO.Path]::IsPathRooted( $Path ) ) { $Path = Join-Path -Path (Get-Location) -ChildPath $Path } $Path = [IO.Path]::GetFullPath( $Path ) ## Check for existence of rivet.json if( -not (Test-Path -Path $Path -PathType Leaf) ) { Write-Error ('Rivet configuration file "{0}" not found.' -f $Path) return } $configRoot = Split-Path -Parent -Path $Path $rawConfig = Get-Content -Raw -Path $Path | ConvertFrom-Json if( -not $rawConfig ) { Write-Error -Message ('Rivet configuration file "{0}" contains invalid JSON.' -f $Path) return } if( $Environment -and -not (Get-Environment) ) { Write-Error ('Environment "{0}" not found in "{1}".' -f $Environment,$Path) return } $errorCount = $Global:Error.Count $sqlServerName = Get-ConfigProperty -Name 'SqlServerName' -Required -AsString $dbsRoot = Get-ConfigProperty -Name 'DatabasesRoot' -Required -AsPath $connectionTimeout = Get-ConfigProperty -Name 'ConnectionTimeout' -AsInt if( $null -eq $connectionTimeout ) { $connectionTimeout = 15 } $commandTimeout = Get-ConfigProperty -Name 'CommandTimeout' -AsInt if( $null -eq $commandTimeout ) { $commandTimeout = 30 } $pluginPaths = Get-ConfigProperty -Name 'PluginPaths' -AsPath -Resolve $ignoredDatabases = Get-ConfigProperty -Name 'IgnoreDatabases' -AsArray $targetDatabases = Get-ConfigProperty -Name 'TargetDatabases' -AsHashtable if( $null -eq $targetDatabases ) { $targetDatabases = @{ } } $order = Get-ConfigProperty -Name 'DatabaseOrder' -AsArray $pluginModules = Get-ConfigProperty -Name 'PluginModules' -AsArray [Rivet.Configuration.Configuration]$configuration = [Rivet.Configuration.Configuration]::New($Path, $Environment, $sqlServerName, $dbsRoot, $connectionTimeout, $commandTimeout, $pluginPaths, $pluginModules) if( $Global:Error.Count -ne $errorCount ) { return } $databaseInfos = Invoke-Command { # Get user-specified databases first if( $Database ) { $Database | Add-Member -MemberType ScriptProperty -Name Name -Value { $this } -PassThru | Add-Member -MemberType ScriptProperty -Name FullName -Value { Join-Path -Path $configuration.DatabasesRoot -ChildPath $this.Name } -PassThru } else { # Then get all of them in the order requested if( $order ) { foreach( $dbName in $order ) { Get-ChildItem -Path $configuration.DatabasesRoot -Filter $dbName -Directory } } Get-ChildItem -Path $configuration.DatabasesRoot -Exclude $order -Directory } } | Select-Object -Property Name,FullName -Unique | Where-Object { if( -not $ignoredDatabases ) { return $true } $dbName = $_.Name $ignore = $ignoredDatabases | Where-Object { $dbName -like $_ } return -not $ignore } foreach( $databaseInfo in $databaseInfos ) { $dbName = $databaseInfo.Name [Rivet.Configuration.Database[]]$rivetDatabases = & { if( $targetDatabases.ContainsKey( $dbName ) ) { foreach( $targetDBName in $targetDatabases[$dbName] ) { [Rivet.Configuration.Database]::New($targetDBName, $databaseInfo.FullName) | Write-Output } } else { [Rivet.Configuration.Database]::New($dbName, $databaseInfo.FullName) | Write-Output } } [void]$configuration.Databases.AddRange( $rivetDatabases ) } return $configuration } function Import-RivetPlugin { [CmdletBinding()] param( [Parameter(Mandatory)] [AllowEmptyCollection()] [String[]]$Path, [Parameter(Mandatory)] [AllowEmptyCollection()] [String[]]$ModuleName ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Write-Timing -Message 'Import-RivetPlugin BEGIN' -Indent $moduleNames = & { foreach( $pluginPath in $Path ) { if( [IO.Path]::GetExtension($pluginPath) -eq '.ps1' ) { Write-Error -Message ('Unable to import Rivet plugin "{0}": invalid plugin file extension. A Rivet plugin must be a PowerShell module. The path to your plugin must be to a directory that is importable by the `Import-Module` command, or to a .psd1 or .psm1 file.' -f $pluginPath) -ErrorAction Stop continue } Write-Timing -Message " Import BEGIN $($pluginPath)" Import-Module -Name $pluginPath -Global -Force -PassThru | Select-Object -ExpandProperty 'Name' | Write-Output Write-Timing -Message " Import END $($pluginPath)" } $ModuleName | Write-Output } $commands = & { foreach( $moduleName in $moduleNames ) { Write-Timing -Message " Get Commands BEGIN $($moduleName)" if( -not (Get-Module -Name $moduleName) ) { $msg = ("Unable to load plugins from module ""$($moduleName)"": the module is not loaded. Please " + 'call "Import-Module" to load this module before running Rivet. If you want Rivet to load the ' + 'module for you, use the "PluginPaths" setting and set it to a list of paths to modules ' + 'that Rivet should import.') Write-Error -Message $msg -ErrorAction Stop continue } Get-Command -Module $moduleName Write-Timing -Message " Get Commands END $($moduleName)" } # Get any global functions that may be plugins. Write-Timing -Message (' Get Functions BEGIN') Get-Command -CommandType Function | Where-Object { -not $_.Module } Write-Timing -Message (' Get Functions End') } $script:plugins = & { foreach( $command in $commands ) { if( -not ($command | Get-Member -Name 'ScriptBlock') ) { continue } if( $command.ScriptBlock.Attributes | Where-Object { $_ -is [Rivet.PluginAttribute] } ) { $command | Write-Output } } foreach( $command in $commands ) { if( -not ($command | Get-Member -Name 'ImplementingType') ) { continue } f( $command.ImplementingType.Attributes | Where-Object { $_ -is [Rivet.PluginAttribute] } ) { $command | Write-Output } } } Write-Timing -Message (' Discovered {0} plugins.' -f ($plugins | Measure-Object).Count) Write-Timing -Message 'Import-RivetPlugin END' -Outdent } function Initialize-Database { <# .SYNOPSIS Intializes the database so that it can be migrated by Rivet. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [Rivet.Configuration.Configuration] $Configuration ) Set-StrictMode -Version 'Latest' $who = ('{0}\{1}' -f $env:USERDOMAIN,$env:USERNAME); $migrationsPath = Join-Path -Path $rivetModuleRoot -ChildPath 'Migrations' Write-Debug -Message ('# {0}.{1}' -f $Connection.DataSource,$Connection.Database) Update-Database -Path $migrationsPath -RivetSchema -Configuration $Configuration } function Invoke-MigrationOperation { <# .SYNOPSIS Runs the SQL created by a `Rivet.Migration` object. .DESCRIPTION All Rivet migrations are described by instances of `Rivet.Migration` objects. These objects eventually make their way here, at which point they are converted to SQL, and executed. .EXAMPLE Invoke-Migration -Operation $operation This example demonstrates how to call `Invoke-Migration` with a migration object. #> [CmdletBinding(DefaultParameterSetName='AsReader')] param( [Parameter(Mandatory)] # The migration this operation is from. [Rivet.Migration]$Migration, [Parameter(Mandatory,ValueFromPipeline)] # The migration object to invoke. [Rivet.Operations.Operation]$Operation ) begin { } process { Set-StrictMode -Version 'Latest' $optionalParams = @{ } $nonQuery = $false $asScalar = $false if( $Operation.QueryType -eq [Rivet.OperationQueryType]::NonQuery -or $Operation.QueryType -eq [Rivet.OperationQueryType]::Ddl ) { $optionalParams['NonQuery'] = $true $nonQuery = $true } elseif( $Operation.QueryType -eq [Rivet.OperationQueryType]::Scalar ) { $optionalParams['AsScalar'] = $true $asScalar = $true } $Operation.ToQuery() | Split-SqlBatchQuery -Verbose:$false | Where-Object { $_ } | ForEach-Object { $batchQuery = $_ $result = $null $rowsAffected = -1 $rowCount = $null try { if( $Operation -is [Rivet.Operations.RemoveRowOperation] -and $Operation.Truncate ) { $rowCount = Invoke-Query -Query ('select count(*) from [{0}].[{1}]' -f $Operation.SchemaName,$Operation.TableName) -AsScalar } $result = Invoke-Query -Query $batchQuery -Parameter $Operation.Parameters -CommandTimeout $Operation.CommandTimeout @optionalParams } catch { Write-RivetError -Message ('Migration {0} failed' -f $migrationInfo.FullName) ` -CategoryInfo $_.CategoryInfo.Category ` -ErrorID $_.FullyQualifiedErrorID ` -Exception $_.Exception ` -CallStack ($_.ScriptStackTrace) ` -Query $batchQuery throw (New-Object ApplicationException 'Migration failed.',$_.Exception) } if( $nonQuery ) { if( $rowCount -eq $null ) { $rowsAffected = $result } } elseif( $asScalar ) { if( $result -ne 0 ) { if( $Operation -is [Rivet.Operations.UpdateCodeObjectMetadataOperation] ) { $exMsg = "Failed to refresh {0}.{1}" -f $Operation.SchemaName,$Operation.Name } elseif( $Operation -is [Rivet.Operations.RenameColumnOperation] ) { $exMsg = "Failed to rename column {0}.{1}.{2} to {0}.{1}.{3}" -f $Operation.SchemaName,$Operation.TableName,$Operation.Name,$Operation.NewName } elseif( $Operation -is [Rivet.Operations.RenameOperation] ) { $exMsg = "Failed to rename object {0}.{1} to {0}.{2}" -f $Operation.SchemaName,$Operation.Name,$Operation.NewName } throw ('{0}: error code {1}' -f $exMsg,$result) } } return New-Object 'Rivet.OperationResult' $Migration,$Operation,$batchQuery,$rowsAffected } } end { } } filter Invoke-Query { <# .SYNOPSIS Executes a SQL query against the database. .DESCRIPTION The `Invoke-Query` function runs arbitrary queries aginst the database. Queries are split on `GO` statements, and each query is sent individually to the database. By default, rows are returned as anonymous PsObjects, with properties for each named column returned. Unnamed columns are given arbitrary `ColumnIdx` names, where `Idx` is a number the increments by one for each anonymous column, beginning with 0. You can return the results as a scalar using the AsScalar parameter. use the `NonQuery` switch to run non-queryies (e.g. `update`, `insert`, etc.). In this case, the number of rows affected by the query is returned. Do not use this method to migrate/transform your database, or issue DDL queries! The queries issued by this function happen before the DDL applied by a migration's operations. Use the `Invoke-Ddl` function instead. If you need to dynamically migrate your database based on its state, use this function to query the state of the database, and the other Rivet operations to perform the migration. You can pipe queries to this method, too! .LINK Invoke-Ddl .EXAMPLE Invoke-Query -Query 'create table rivet.Migrations( )' Executes the create table syntax above against the database. .EXAMPLE Invoke-Query -Query 'select count(*) from MyTable' -Database MyOtherDatabase Executes a query against the non-current database. Returns the rows as objects. .EXAMPLE 'select count(*) from sys.tables' | Invoke-Query -AsScalar Demonstrates how queries can be piped into `Invoke-Query`. Also shows how a result can be returned as a scalar. #> [CmdletBinding(DefaultParameterSetName='AsReader')] param( [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)] [string] $Query, [Parameter()] [Hashtable] $Parameter, [Parameter(Mandatory=$true,ParameterSetName='ExecuteScalar')] [Switch] $AsScalar, [Parameter(Mandatory=$true,ParameterSetName='ExecuteNonQuery')] [Switch] $NonQuery, [UInt32] # The time in seconds to wait for the command to execute. The default is 30 seconds. $CommandTimeout = 30 ) Set-StrictMode -Version 'Latest' $Query | Split-SqlBatchQuery -Verbose:$false | Where-Object { $_ } | ForEach-Object { $queryBatch = $_ $cmd = New-Object 'Data.SqlClient.SqlCommand' ($queryBatch,$Connection,$Connection.Transaction) $cmdStartedAt = [DateTime]::UtcNow try { $cmd.CommandTimeout = $CommandTimeout if( $Parameter ) { $Parameter.Keys | ForEach-Object { $name = $_ $value = $Parameter[$name] if( -not $name.StartsWith( '@' ) ) { $name = '@{0}' -f $name } [void] $cmd.Parameters.AddWithValue( $name, $value ) } } if( $PSCmdlet.ParameterSetName -eq 'ExecuteNonQuery' ) { $cmd.ExecuteNonQuery() } elseif( $PSCmdlet.ParameterSetName -eq 'ExecuteScalar' ) { $cmd.ExecuteScalar() } else { $cmdReader = $cmd.ExecuteReader() try { if( $cmdReader.HasRows ) { while( $cmdReader.Read() ) { $row = @{ } for ($i= 0; $i -lt $cmdReader.FieldCount; $i++) { $name = $cmdReader.GetName( $i ) if( -not $name ) { $name = 'Column{0}' -f $i } $value = $cmdReader.GetValue($i) if( $cmdReader.IsDBNull($i) ) { $value = $null } $row[$name] = $value } New-Object PsObject -Property $row } } } finally { $cmdReader.Close() } } } finally { $cmd.Dispose() $queryLines = $queryBatch -split ([TExt.RegularExpressions.Regex]::Escape([Environment]::NewLine)) Write-Verbose -Message ('{0,8} (ms) {1}' -f ([int]([DateTime]::UtcNow - $cmdStartedAt).TotalMilliseconds),($queryLines | Select-Object -First 1)) $queryLines | Select-Object -Skip 1 | ForEach-Object { Write-Verbose -Message ('{0} {1}' -f (' ' * 13),$_) } } } } function Invoke-Rivet { [CmdletBinding(SupportsShouldProcess=$True)] param( [Parameter(Mandatory=$true,ParameterSetName='New')] [Switch] # Creates a new migration. $New, [Parameter(Mandatory=$true,ParameterSetName='Push')] [Switch] # Applies migrations. $Push, [Parameter(Mandatory=$true,ParameterSetName='Pop')] [Parameter(Mandatory=$true,ParameterSetName='PopByCount')] [Parameter(Mandatory=$true,ParameterSetName='PopByName')] [Parameter(Mandatory=$true,ParameterSetName='PopAll')] [Switch] # Reverts migrations. $Pop, [Parameter(Mandatory=$true,ParameterSetName='Redo')] [Switch] # Reverts a migration, then re-applies it. $Redo, [Parameter(Mandatory=$true,ParameterSetName='New',Position=1)] [Parameter(ParameterSetName='Push',Position=1)] [Parameter(Mandatory=$true,ParameterSetName='PopByName',Position=1)] [ValidateLength(1,241)] [string[]] # The name of the migrations to create, push, or pop. Matches against the migration's ID, Name, or file name (without extension). Wildcards permitted. $Name, [Parameter(Mandatory=$true,ParameterSetName='PopByCount',Position=1)] [UInt32] # The number of migrations to pop. Default is 1. $Count = 1, [Parameter(Mandatory=$true,ParameterSetName='PopAll')] [Switch] # Pop all migrations $All, [Parameter(ParameterSetName='Pop')] [Parameter(ParameterSetName='PopByCount')] [Parameter(ParameterSetName='PopByName')] [Parameter(ParameterSetName='PopAll')] [Parameter(ParameterSetName='DropDatabase')] [Switch] # Force popping a migration you didn't apply or that is old. $Force, [Parameter(ParameterSetName='New',Position=2)] [Parameter(ParameterSetName='Push')] [Parameter(ParameterSetName='Pop')] [Parameter(ParameterSetName='PopByCount')] [Parameter(ParameterSetName='PopByName')] [Parameter(ParameterSetName='PopAll')] [Parameter(ParameterSetName='Redo')] [Parameter(ParameterSetName='DropDatabase')] [string[]] # The database(s) to migrate. Optional. Will operate on all databases otherwise. $Database, [Parameter(ParameterSetName='New')] [Parameter(ParameterSetName='Push')] [Parameter(ParameterSetName='Pop')] [Parameter(ParameterSetName='PopByCount')] [Parameter(ParameterSetName='PopByName')] [Parameter(ParameterSetName='PopAll')] [Parameter(ParameterSetName='Redo')] [Parameter(ParameterSetName='DropDatabase')] [string] # The environment you're working in. Controls which settings Rivet loads from the `rivet.json` configuration file. $Environment, [Parameter(ParameterSetName='New')] [Parameter(ParameterSetName='Push')] [Parameter(ParameterSetName='Pop')] [Parameter(ParameterSetName='PopByCount')] [Parameter(ParameterSetName='PopByName')] [Parameter(ParameterSetName='PopAll')] [Parameter(ParameterSetName='Redo')] [Parameter(ParameterSetName='DropDatabase')] [string] # The path to the Rivet configuration file. Default behavior is to look in the current directory for a `rivet.json` file. See `about_Rivet_Configuration` for more information. $ConfigFilePath, [Parameter(ParameterSetName='DropDatabase')] [Switch] # Drops the database(s) for the current environment when given. User will be prompted for confirmation when used. $DropDatabase ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState [Rivet.Configuration.Configuration]$settings = Get-RivetConfig -Database $Database -Path $ConfigFilePath -Environment $Environment if( -not $settings.Databases ) { Write-Error (@' Found no databases to migrate. This can be a few things: * There are no database directories in ''{0}''. Please create a database directory there or supply an explicit database name with the `Database` parameter. * You supplied an explicit database name, but that database is on the ignore list. Remove it from the ignore list in ''{1}'' or enter a database name that isn't ignored. * You supplied an explicit database name, but no directory for migrations exist on the file system (under {0}). Create a migrations directory or enter the name of a database that exists. '@ -f $settings.DatabasesRoot,$settings.Path) return } Import-RivetPlugin -Path $settings.PluginPaths -ModuleName $settings.PluginModules try { if( $PSCmdlet.ParameterSetName -eq 'New' ) { $settings.Databases | Select-Object -ExpandProperty 'MigrationsRoot' -Unique | ForEach-Object { New-Migration -Name $Name -Path $_ } return } if( $DropDatabase ) { # Connect to master as we cannot drop a database if we're connected to it Connect-Database -SqlServerName $settings.SqlServerName ` -Database 'Master' ` -ConnectionTimeout $settings.ConnectionTimeout $databaseString = ($settings.Databases | Select-Object -ExpandProperty 'Name') -join "', '" $query = "select name from sys.databases where name in ('$($databaseString)')" $databaseList = Invoke-Query -Query $query if( $databaseList ) { $confirmDropDatabase = $false if( -not $Force) { $confirmQuery = 'Using the `DropDatabase` switch will drop the database(s) for the current ' + 'environment. Do you want to proceed?' $confirmCaption = 'Drop the following database(s)? ' + (($databaseList | Select-Object -ExpandProperty 'Name') -join ', ') $confirmDropDatabase = $PSCmdlet.ShouldContinue( $confirmQuery, $confirmCaption ) } if( $confirmDropDatabase -or $Force ) { foreach( $databaseItem in $databaseList ) { $query = "DROP DATABASE {0}" -f $databaseItem.Name Invoke-Query -Query $query } } } return } foreach( $databaseItem in $settings.Databases ) { $databaseName = $databaseItem.Name $dbMigrationsPath = $databaseItem.MigrationsRoot $result = Connect-Database -SqlServerName $settings.SqlServerName ` -Database $databaseName ` -ConnectionTimeout $settings.ConnectionTimeout if( -not $result ) { continue } try { Initialize-Database -Configuration $settings $updateParams = @{ Path = $dbMigrationsPath; Configuration = $settings; } if( -not (Test-Path -Path $dbMigrationsPath -PathType Container) ) { Write-Warning ('{0} database migrations directory ({1}) not found.' -f $databaseName,$dbMigrationsPath) continue } if( $PSBoundParameters.ContainsKey('Name') ) { $updateParams.Name = $Name # Join-Path $dbMigrationsPath ("*_{0}.ps1" -f $Name) } Write-Debug -Message ('# {0}.{1}' -f $Connection.DataSource,$Connection.Database) if( $pscmdlet.ParameterSetName -eq 'Push' ) { Update-Database @updateParams } elseif( $pscmdlet.ParameterSetName -eq 'Pop' ) { Update-Database -Pop -Count 1 -Force:$Force @updateParams } elseif( $pscmdlet.ParameterSetName -eq 'PopByName' ) { Update-Database -Pop -Force:$Force @updateParams } elseif( $pscmdlet.ParameterSetName -eq 'PopByCount' ) { Update-Database -Pop -Count $Count -Force:$Force @updateParams } elseif ( $pscmdlet.ParameterSetName -eq 'PopAll' ) { Update-Database -Pop -All -Force:$Force @updateParams } elseif( $pscmdlet.ParameterSetName -eq 'Redo' ) { Update-Database -Pop -Count 1 @updateParams Update-Database @updateParams } } catch { $firstException = $_.Exception while( $firstException.InnerException ) { $firstException = $firstException.InnerException } Write-Error ('{0} database migration failed: {1}.' -f $databaseName,$firstException.Message) } } } finally { Disconnect-Database } } Set-Alias -Name rivet -Value Invoke-Rivet function Invoke-RivetPlugin { param( [Parameter(Mandatory)] [Rivet.Events] $Event, [hashtable] $Parameter ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Write-Timing -Message 'Invoke-RivetPlugin BEGIN' -Indent $responders = $plugins | Where-Object { $_.ScriptBlock.Attributes | Where-Object { $_ -is [Rivet.PluginAttribute] -and $_.RespondsTo -eq $Event } } try { if( -not $responders ) { return } foreach( $plugin in $responders ) { foreach( $parameterName in $Parameter.Keys ) { if( -not $plugin.Parameters.ContainsKey($parameterName) ) { Write-Error -Message ('The function "{0}" that responds to Rivet''s "{1}" event must have a named "{2}" parameter. Please update this function''s definition.' -f $plugin.Name,$Event,$parameterName) -ErrorAction Stop } } & $plugin.Name @Parameter Write-Timing -Message (' {0}' -f $plugin.Name) } } finally { Write-Timing -Message 'Invoke-RivetPlugin END' -Outdent } } function Merge-Migration { <# .SYNOPSIS Creates a cumulative set of operations from migration scripts. .DESCRIPTION The `Merge-Migration` functions creates a cumulative set of migrations from migration scripts. If there are multiple operations across one or more migration scripts that touch the same database object, those changes are combined into one operation. For example, if you create a table in one migration, add a column in another migrations, then remove a column in a third migration, this function will output an operation that represents the final state for the object: a create table operation that includes the added column and doesn't include the removed column. In environments where tables are replicated, it is more efficient to modify objects once and have that change replicated once, than to have the same object modified multiple times and replicated multiple times. This function returns `Rivet.Migration` objects. Each object will have zero or more operations in its `PushOperations` property. If there are zero operations, it means the original operation was consolidated into another migration. Each operation has `Source` member on it, which is a list of all the migrations that contributed to that operation. .OUTPUTS Rivet.Migration .EXAMPLE Get-Migration | Merge-Migration Demonstrates how to run `Merge-Migration`. It is always used in conjunction with `Get-Migration`. #> [CmdletBinding()] [OutputType([Rivet.Migration])] param( [Parameter(ValueFromPipeline)] # The path to the rivet.json file to use. By default, it will look in the current directory. [Rivet.Migration[]]$Migration ) begin { Set-StrictMode -Version 'Latest' # Collect all the migrations. We can't merge anything until we get to the end. [Collections.ArrayList]$migrations = [Collections.ArrayList]::New() [Collections.Generic.List[Rivet.Operations.Operation]]$allOperations = [Collections.Generic.List[Rivet.Operations.Operation]]::New() } process { foreach( $migrationItem in $Migration ) { [void]$migrations.Add($migrationItem) foreach( $op in $migrationItem.PushOperations ) { for( $idx = $allOperations.Count - 1; $idx -ge 0; --$idx) { $allOperations[$idx].Merge($op) } [void]$allOperations.Add($op) } } } end { foreach( $migrationItem in $migrations ) { for( $idx = $migrationItem.PushOperations.Count - 1; $idx -ge 0 ; --$idx ) { $operation = $migrationItem.PushOperations[$idx] if( $operation.Disabled ) { $migrationItem.PushOperations.RemoveAt($idx) } } $migrationItem | Write-Output } } } function New-ConstraintName { <# .SYNOPSIS Creates a default constraint name for a column in a table. #> [CmdletBinding(DefaultParameterSetName='DF')] param( [Parameter(Mandatory,ParameterSetName='DF')] # Creates a default constraint name. [switch]$Default, [Parameter(Mandatory,ParameterSetName='PK')] # Creates a primary key name. [switch]$PrimaryKey, [Parameter(Mandatory,ParameterSetName='IX')] # Creates an index name. [switch]$Index, [Parameter(ParameterSetName='IX')] # For a unique index. [switch]$Unique, [Parameter(Mandatory,ParameterSetName='AK')] # Creates an unique key/alternate key constraint name. [switch]$UniqueKey, [Parameter(Mandatory,ParameterSetName='FK')] # Creates a foreign key constraint name. [switch]$ForeignKey, # The table's schema. Default is `dbo`. [String]$SchemaName = 'dbo', [Parameter(Mandatory,Position=0)] # The table name. [String]$TableName, [Parameter(Mandatory,ParameterSetName='DF',Position=1)] [Parameter(Mandatory,ParameterSetName='IX',Position=1)] [Parameter(Mandatory,ParameterSetName='AK',Position=1)] [Parameter(Mandatory,ParameterSetName='UIX',Position=1)] # The column name. [String[]]$ColumnName, [Parameter(ParameterSetName='FK')] [String]$ReferencesSchemaName = 'dbo', [Parameter(Mandatory,ParameterSetName='FK',Position=1)] [String]$ReferencesTableName ) Set-StrictMode -Version 'Latest' $op = switch( $PSCmdlet.ParameterSetName ) { 'DF' { [Rivet.ConstraintName]::new($SchemaName, $TableName, $ColumnName, [Rivet.ConstraintType]::Default) } 'FK' { [Rivet.ForeignKeyConstraintName]::new($SchemaName, $TableName, $ReferencesSchemaName, $ReferencesTableName) } 'PK' { [Rivet.ConstraintName]::new($SchemaName, $TableName, $null, [Rivet.ConstraintType]::PrimaryKey) } 'IX' { [Rivet.IndexName]::new($SchemaName, $TableName, $ColumnName, $Unique) } 'AK' { [Rivet.ConstraintName]::new($SchemaName, $TableName, $ColumnName, [Rivet.ConstraintType]::UniqueKey) } } return $op.Name } function New-Migration { <# .SYNOPSIS Creates a new migration script. .DESCRIPTION Creates a migration script with a given name. The script is prefixed with the current timestamp (e.g. yyyyMMddHHmmss). The script is created in `$Path\$Database\Migrations`. #> param( [Parameter(Mandatory=$true)] [string[]] # The name of the migration to create. $Name, [Parameter(Mandatory=$true)] [string] # The path to the directory where the migration should be saved. $Path ) foreach( $nameItem in $Name ) { $id = $null $id = [int64](Get-Date).ToString('yyyyMMddHHmmss') while( (Test-Path -Path $Path -PathType Container) -and ` (Get-ChildItem -Path $Path -Filter ('{0}_*' -f $id) ) ) { $id++ } $filename = '{0}_{1}.ps1' -f $id,$nameItem $importRivetPath = Join-Path -Path $rivetModuleRoot -ChildPath 'Import-Rivet.ps1' -Resolve $migrationPath = Join-Path -Path $Path -ChildPath $filename $migrationPath = [IO.Path]::GetFullPath( $migrationPath ) New-Item -Path $migrationPath -Force -ItemType File $template = @" <# Your migration is ready to go! For the best development experience, please write your migration in the PowerShell 3 ISE. Run the following at a PowerShell prompt: PS> ise "{0}" or right-click the migration in Windows Explorer and choose "Edit". The PowerShell ISE gives you intellisense, auto-complete, and other features you may be used to from the Visual Studio IDE. Use this command in the ISE to import Rivet and get intellisense/auto-complete: PSISE> {1} The ISE has a "Show Command" add-on which will let you build your migration with a GUI. Once you've got Rivet imported, choose View > Show Command Add-on. When the Show Command Add-on appears, choose 'Rivet' from the module. Click on a migration operation to build it with the Show Command GUI. #> function Push-Migration {{ }} function Pop-Migration {{ }} "@ -f $migrationPath,$importRivetPath $template | Set-Content -Path $migrationPath } } function New-MigrationObject { <# .SYNOPSIS Creates a new `Rivet.Migration` object, suitable for passing to `Invoke-Migration` function. .DESCRIPTION All migrations in Rivet should be represented as an object. Each object should inherit from `Rivet.Migration`. This method returns an empty `Rivet.Migration` object, which is typically used to create migration-specific properties/methods. .EXAMPLE $migration = New-MigrationObject Returns a `Rivet.Migration` object. #> [CmdletBinding()] param( [Parameter()] [hashtable] # The properties on the object. $Property, [Parameter(Mandatory=$true)] [ScriptBlock] # The script block to execute as the ToQuery method. $ToQueryMethod ) $o = New-Object 'Rivet.Migration' '','','','' $Property.Keys | ForEach-Object { $o | Add-Member -MemberType NoteProperty -Name $_ -Value $Property.$_ } $o | Add-Member -MemberType ScriptMethod -Name 'ToQuery' -Value $ToQueryMethod -PassThru } function Repair-Operation { [CmdletBinding()] [OutputType([Rivet.Operations.Operation])] param( [Parameter(Mandatory,ValueFromPipeline)] [Rivet.Operations.Operation]$Operation ) begin { Set-StrictMode -Version 'Latest' function Repair-DefaultConstraintName { param( [Parameter(Mandatory,ValueFromPipeline)] [Rivet.Column]$Column ) begin { Set-StrictMode -Version 'Latest' $operationName = 'Add-Table' if( $Operation -is [Rivet.Operations.UpdateTableOperation] ) { $operationName = 'Update-Table' } } process { if( -not $Column.DefaultExpression -or ($Column.DefaultExpression -and $Column.DefaultConstraintName) ) { return } $column.DefaultConstraintName = New-ConstraintName -Default ` -SchemaName $schemaName ` -TableName $name ` -ColumnName $column.Name Write-Warning -Message ('Column default constraint names will be required in a future version of ' + "Rivet. Add a ""DefaultConstraintName"" parameter to the [$($Column.Name)] " + "column on the $($operationName) operation for the " + "[$($schemaName)].[$($name)] table.") } } } process { $name = $Operation | Select-Object -ExpandProperty 'Name' -ErrorAction Ignore # If a constraint operation already has a name, don't do anything. if( $name -and $Operation -isnot [Rivet.Operations.AddTableOperation] -and $Operation -isnot [Rivet.Operations.UpdateTableOperation] ) { return $Operation } $schemaName = $Operation | Select-Object -ExpandProperty 'SchemaName' -ErrorAction Ignore $tableName = $Operation | Select-Object -ExpandProperty 'TableName' -ErrorAction Ignore $columnName = $Operation | Select-Object -ExpandProperty 'ColumnName' -ErrorAction Ignore $columnDesc = $columnName -join '", "' $pluralSuffix = '' if( ($columnName | Measure-Object).Count -gt 1 ) { $pluralSuffix = 's' } $tableDesc = "[$($schemaName)].[$($tableName)]" $warningMsg = '' switch( $Operation.GetType().Name ) { 'AddDefaultConstraintOperation' { $Operation.Name = New-ConstraintName -Default -SchemaName $schemaName -TableName $tableName -ColumnName $columnName $warningMsg = "Default constraint names will be required in a future version of Rivet. Add a " + """Name"" parameter (with a value of ""$($Operation.Name)"") to the Add-DefaultConstraint " + "operation for the $($tableDesc) table's ""$($columnDesc)"" column." } 'AddForeignKeyOperation' { $Operation.Name = New-ConstraintName -ForeignKey ` -SchemaName $schemaName ` -TableName $tableName ` -ReferencesSchemaName $Operation.ReferencesSchemaName ` -ReferencesTableName $Operation.ReferencesTableName $warningMsg = "Foreign key constraint names will be required in a future version of Rivet. " + "Add a ""Name"" parameter (with a value of ""$($Operation.Name)"") to the Add-ForeignKey " + "operation for the $($tableDesc) table's $($columnDesc) column$($pluralSuffix)." } 'AddIndexOperation' { $Operation.Name = New-ConstraintName -Index -SchemaName $schemaName -TableName $tableName -ColumnName $columnName -Unique:$Operation.Unique $warningMsg = "Index names will be required in a future version of Rivet. Add a ""Name"" " + "parameter (with a value of ""$($Operation.Name)"") to the Add-Index operation for the " + "$($tableDesc) table's ""$($columnDesc)"" column$($pluralSuffix)." } 'AddPrimaryKeyOperation' { $Operation.Name = New-ConstraintName -PrimaryKey -SchemaName $schemaName -TableName $tableName $warningMsg = "Primary key constraint names will be required in a future version of Rivet. " + "Add a ""Name"" parameter (with a value of ""$($Operation.Name)"") to the Add-PrimaryKey " + "operation for the $($tableDesc) table's $($columnDesc) column." } 'AddTableOperation' { $Operation.Columns | Repair-DefaultConstraintName } 'AddUniqueKeyOperation' { $Operation.Name = New-ConstraintName -UniqueKey -SchemaName $schemaName -TableName $tableName -ColumnName $columnName $warningMsg = "Unique key constraint names will be required in a future version of Rivet. Add " + "a ""Name"" parameter (with a value of ""$($Operation.Name)"") to the Add-UniqueKey " + "operation on the $($tableDesc) table's $($columnDesc) column$($pluralSuffix)." } 'RemoveDefaultConstraint' { $Operation.Name = New-ConstraintName -Default -SchemaName $schemaName -TableName $tableName -ColumnName $columnName $warningMsg = "Default constraint names will be required in a future version of Rivet. Add a " + """Name"" parameter (with a value of ""$($Operation.Name)"") to the Remove-DefaultConstraint " + "operation for the $($tableDesc) table's ""$($columnDesc)"" column." } 'RemoveForeignKeyOperation' { $Operation.Name = New-ConstraintName -ForeignKey -SchemaName $schemaName ` -TableName $tableName ` -ReferencesSchema $Operation.ReferencesSchema ` -ReferencesTableName $Operation.ReferencesTableName $warningMsg = "Foreign key constraint names will be required in a future version of Rivet. " + "Add a ""Name"" parameter (with a value of ""$($Operation.Name)"") to the Remove-ForeignKey " + "operation for the $($tableDesc) table that references the " + "[$($Operation.ReferencesSchemaName)].[$($Operation.ReferencesTableName)] table." } 'RemoveIndexOperation' { $Operation.Name = New-ConstraintName -Index -SchemaName $schemaName -TableName $tableName -ColumnName $columnName -Unique:$Operation.Unique $warningMsg = "Index names will be required in a future version of Rivet. Add a ""Name"" " + "parameter (with a value of ""$($Operation.Name)"") to the Remove-Index operation for the " + "$($tableDesc) table's ""$($columnDesc)"" column$($pluralSuffix)." } 'RemovePrimaryKeyOperation' { $Operation.Name = New-ConstraintName -PrimaryKey -SchemaName $schemaName -TableName $tableName $warningMsg = "Primay key constraint names will be required in a future version of Rivet. " + "Add a ""Name"" parameter (with a value of ""$($Operation.Name)"") to the Remove-PrimaryKey " + "operation for the $($tableDesc) table." } 'RemoveUniqueKeyOperation' { $Operation.Name = New-ConstraintName -UniqueKey -SchemaName $schemaName -TableName $tableName -ColumnName $columnName $warningMsg = "Unique key constraint names will be required in a future version of Rivet. " + "Remove the ""ColumnName"" parameter and add a ""Name"" parameter (with a value of " + """$($Operation.Name)"") to the Remove-UniqueKey operation for the " + "$($tableDesc) table's ""$($columnDesc)"" column$($pluralSuffix)." } 'UpdateTableOperation' { $Operation.AddColumns | Repair-DefaultConstraintName } } if( $warningMsg ) { Write-Warning -Message $warningMsg } return $Operation } end { } } function Split-SqlBatchQuery { <# .SYNOPSIS Splits a SQL batch query into individual queries. .DESCRIPTION `Split-SqlBatchQuery` takes a batch query and splits it by the `GO` statements it contains. `GO` statements inside comments and strings are ignored. It does not use regular expressions. If the query has no `GO` statements, you'll get your original query back. You can pipe SQL batch queries into this function and you'll get runnable queries out the other side. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)] [string] $Query ) begin { } process { Set-StrictMode -Version 'Latest' $currentQuery = New-Object 'Text.StringBuilder' $inSingleLineComment = $false $inMultiLineComment = $false $inString = $false $stringCouldBeEnding = $false $prevChar = $null $currentChar = $null $commentDepth = 0 $currentLine = New-Object 'Text.StringBuilder' function Complete-Line { Write-Debug -Message ("inMultiLineComment: {0}; inSingleLineComment: {1}; inString {2}; {3}" -f $inMultiLineComment,$inSingleLineComment,$inString,$currentLine.ToString()) $trimmedLine = $currentLine.ToString().Trim() if( $trimmedLine -notmatch "^GO\b" ) { [void]$currentQuery.Append( $currentLine ) } $currentLine.Length = 0 if( $trimmedLine -match "^GO\b" -or $atLastChar ) { $currentQuery.ToString() $currentQuery.Length = 0 } } $chars = $Query.ToCharArray() for( $idx = 0; $idx -lt $chars.Count; ++$idx ) { $prevChar = $null if( $idx -gt 1 ) { $prevChar = $chars[$idx - 1] } $currentChar = $chars[$idx] $nextChar = $null if( $idx + 1 -lt $chars.Count ) { $nextChar = $chars[$idx + 1] } $atLastChar = $idx -eq $chars.Count - 1 if( $atLastChar ) { [void]$currentLine.Append( $currentChar ) Complete-Line continue } if( $inMultiLineComment ) { [void] $currentLine.Append( $currentChar ) if( $prevChar -eq '/' -and $currentChar -eq '*' ) { Write-Debug -Message ('Entering nested multi-line comment.') $commentDepth++ continue } elseif( $prevChar -eq '*' -and $currentChar -eq '/' ) { Write-Debug -Message ('Leaving multi-line comment.') $commentDepth-- $inMultiLineComment = ($commentDepth -gt 0) } if( -not $inMultiLineComment ) { Write-Debug -Message ('Multi-line comment closed.') } continue } if( $inSingleLineComment ) { if( $currentChar -eq "`n" ) { Write-Debug -Message ('Leaving single-line comment.') $inSingleLineComment = $false } else { [void] $currentLine.Append( $currentChar ) continue } } if( $inString ) { if( $stringCouldBeEnding ) { $stringCouldBeEnding = $false if( $currentChar -eq "'" ) { [void] $currentLine.Append( $currentChar ) Write-Debug -Message ('Found escaped quote.') continue } else { Write-Debug -Message ('Leaving string.') $inString = $false } } elseif( $currentChar -eq "'" ) { [void] $currentLine.Append( $currentChar ) $stringCouldBeEnding = $true continue } else { [void]$currentLine.Append( $currentChar ) continue } } if( $prevChar -eq "/" -and $currentChar -eq "*" ) { Write-Debug -Message ('Entering multi-line comment.') $inMultiLineComment = $true $commentDepth++ } elseif( $prevChar -eq '-' -and $currentChar -eq '-' ) { Write-Debug -Message ('Entering single-line comment.') $inSingleLineComment = $true } elseif( $currentChar -eq "'" ) { Write-Debug -Message ('Entering string.') $inString = $true } [void] $currentLine.Append( $currentChar ) if( $currentChar -eq "`n" -or $atLastChar ) { Complete-Line } } } end { } } function Test-Migration { <# .SYNOPSIS Tests if a migration was applied to the database. .DESCRIPTION Returns `true` if a migration with the given ID has already been applied. `False` otherwise. .EXAMPLE Test-Migration -ID 20120211235838 Returns `True` if a migration with ID `20120211235838` already exists or `False` if it doesn't. #> param( [Parameter(Mandatory=$true)] [Int64] $ID, [Switch] # Returns the migration info. $PassThru ) $query = 'select ID, Name, Who, AtUtc from {0} where ID=@ID' -f $RivetMigrationsTableFullName,$ID $info = Invoke-Query -Query $query -Parameter @{ ID = $ID } -Verbose:$false if( $info ) { Write-Debug -Message ('{0} {1,-35} {2,14:00000000000000}_{3}' -f $info.AtUtc.ToLocalTime().ToString('yyyy-mm-dd HH:mm'),$info.Who,$info.ID,$info.Name) if( $PassThru ) { return $info } return $true } return $false } function Update-Database { <# .SYNOPSIS Applies a set of migrations to the database. .DESCRIPTION By default, applies all unapplied migrations to the database. You can reverse all migrations with the `Down` switch. .EXAMPLE Update-Database -Path C:\Projects\Rivet\Databases\Rivet\Migrations Applies all un-applied migrations from the `C:\Projects\Rivet\Databases\Rivet\Migrations` directory. .EXAMPLE Update-Database -Path C:\Projects\Rivet\Databases\Rivet\Migrations -Pop Reverses all migrations in the `C:\Projects\Rivet\Databases\Rivet\Migrations` directory #> [CmdletBinding(DefaultParameterSetName='Push', SupportsShouldProcess=$True)] param( [Parameter(Mandatory=$true)] [Rivet.Configuration.Configuration] $Configuration, [Parameter(Mandatory=$true)] [string[]] # The path to the migration. $Path, [Parameter(Mandatory=$true,ParameterSetName='Pop')] [Parameter(Mandatory=$true,ParameterSetName='PopByName')] [Parameter(Mandatory=$true,ParameterSetName='PopByCount')] [Parameter(Mandatory=$true,ParameterSetName='PopAll')] [Switch] # Reverse the given migration(s). $Pop, [Parameter(ParameterSetName='Push')] [Parameter(Mandatory=$true,ParameterSetName='PopByName')] [string[]] $Name, [Parameter(Mandatory=$true,ParameterSetName='PopByCount')] [UInt32] # Reverse the given migration(s). $Count, [Parameter(Mandatory=$true,ParameterSetName='PopAll')] [Switch] # Reverse the given migration(s). $All, [Switch] # Running internal Rivet migrations. This is for internal use only. If you use this flag, Rivet will break when you upgrade. You've been warned! $RivetSchema, [Parameter(ParameterSetName='Pop')] [Parameter(ParameterSetName='PopByCount')] [Parameter(ParameterSetName='PopByName')] [Parameter(ParameterSetName='PopAll')] [Switch] # Force popping a migration you didn't apply or that is old. $Force ) Set-StrictMode -Version 'Latest' function ConvertTo-RelativeTime { param( [Parameter(Mandatory=$true)] [DateTime] # The date time to convert to a relative time string. $DateTime ) [TimeSpan]$howLongAgo = (Get-Date) - $DateTime $howLongAgoMsg = New-Object 'Text.StringBuilder' if( $howLongAgo.Days ) { [void] $howLongAgoMsg.AppendFormat('{0} day', $howLongAgo.Days) if( $howLongAgo.Days -ne 1 ) { [void] $howLongAgoMsg.Append('s') } [void] $howLongAgoMsg.Append(', ') } if( $howLongAgo.Days -or $howLongAgo.Hours ) { [void] $howLongAgoMsg.AppendFormat('{0} hour', $howLongAgo.Hours) if( $howLongAgo.Hours -ne 1 ) { [void] $howLongAgoMsg.Append('s') } [void] $howLongAgoMsg.Append(', ') } if( $howLongAgo.Days -or $howLongAgo.Hours -or $howLongAgo.Minutes ) { [void] $howLongAgoMsg.AppendFormat('{0} minute', $howLongAgo.Minutes) if( $howLongAgo.Minutes -ne 1 ) { [void] $howLongAgoMsg.Append('s') } [void] $howLongAgoMsg.Append(', ') } [void] $howLongAgoMsg.AppendFormat('{0} second', $howLongAgo.Seconds) if( $howLongAgo.Minutes -ne 1 ) { [void] $howLongAgoMsg.Append('s') } [void] $howLongAgoMsg.Append( ' ago' ) return $howLongAgoMsg.ToString() } $popping = ($PSCmdlet.ParameterSetName -like 'Pop*') $numPopped = 0 $who = ('{0}\{1}' -f $env:USERDOMAIN,$env:USERNAME); #$matchedNames = @{ } $byName = @{ } if( $PSBoundParameters.ContainsKey('Name') ) { $byName['Include'] = $Name } $query = 'if (object_id(''{0}'', ''U'') is not null) select ID, Name, Who, AtUtc from {0}' -f $RivetMigrationsTableFullName $appliedMigrations = @{} foreach( $migration in (Invoke-Query -Query $query) ) { $appliedMigrations[$migration.ID] = $migration } $migrations = Get-MigrationFile -Path $Path -Configuration $Configuration @byName -ErrorAction Stop | Sort-Object -Property 'MigrationID' -Descending:$popping | Where-Object { if( $RivetSchema ) { return $true } if( [int64]$_.MigrationID -lt 1000000000000 ) { Write-Error ('Migration "{0}" has an invalid ID. IDs lower than 01000000000000 are reserved for internal use.' -f $_.FullName) -ErrorAction Stop return $false } return $true } | Where-Object { $migration = $appliedMigrations[$_.MigrationID] if( $popping ) { if( $PSCmdlet.ParameterSetName -eq 'PopByCount' -and $numPopped -ge $Count ) { return $false } $numPopped++ # Don't need to pop if migration hasn't been applied. if( -not $migration ) { return $false } $youngerThan = ((Get-Date).ToUniversalTime()) - (New-TimeSpan -Minutes 20) if( $migration.Who -ne $who -or $migration.AtUtc -lt $youngerThan ) { $howLongAgo = ConvertTo-RelativeTime -DateTime ($migration.AtUtc.ToLocalTime()) $confirmQuery = "Are you sure you want to pop migration {0} from database {1} on {2} applied by {3} {4}?" -f $_.FullName,$Connection.Database,$Connection.DataSource,$migration.Who,$howLongAgo $confirmCaption = "Pop Migration {0}?" -f $_.FullName if( -not $Force -and -not $PSCmdlet.ShouldContinue( $confirmQuery, $confirmCaption ) ) { return $false } } return $true } # Only need to parse/push if migration hasn't already been pushed. if( $migration ) { return $false } return $true } | Convert-FileInfoToMigration -Configuration $Configuration foreach( $migrationInfo in $migrations ) { $migrationInfo.DataSource = $Connection.DataSource try { $Connection.Transaction = $Connection.BeginTransaction() if( $Pop ) { $operations = $migrationInfo.PopOperations $action = 'Pop' $sprocName = 'RemoveMigration' } else { $operations = $migrationInfo.PushOperations $action = 'Push' $sprocName = 'InsertMigration' } if( -not $operations.Count ) { Write-Error ('{0} migration''s {1}-Migration function is empty.' -f $migrationInfo.FullName,$action) return } $operations | Invoke-MigrationOperation -Migration $migrationInfo $query = 'exec [rivet].[{0}] @ID = @ID, @Name = @Name, @Who = @Who, @ComputerName = @ComputerName' -f $sprocName $parameters = @{ ID = [int64]$migrationInfo.ID; Name = $migrationInfo.Name; Who = $who; ComputerName = $env:COMPUTERNAME; } Invoke-Query -Query $query -NonQuery -Parameter $parameters | Out-Null $target = '{0}.{1}' -f $Connection.DataSource,$Connection.Database $operation = '{0} migration {1} {2}' -f $PSCmdlet.ParameterSetName,$migrationInfo.ID,$migrationInfo.Name if ($PSCmdlet.ShouldProcess($target, $operation)) { $Connection.Transaction.Commit() } else { $Connection.Transaction.Rollback() break } } catch { $Connection.Transaction.Rollback() # TODO: Create custom exception for migration query errors so that we can report here when unknown things happen. if( $_.Exception -isnot [ApplicationException] ) { Write-RivetError -Message ('Migration {0} failed' -f $migrationInfo.Path) -CategoryInfo $_.CategoryInfo.Category -ErrorID $_.FullyQualifiedErrorID -Exception $_.Exception -CallStack ($_.ScriptStackTrace) } break } finally { $Connection.Transaction = $null } } } # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. function Use-CallerPreference { <# .SYNOPSIS Sets the PowerShell preference variables in a module's function based on the callers preferences. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, including preferences set by common parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't get passed into any function that belongs to a module. When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the function's caller: * ErrorAction * Debug * Confirm * InformationAction * Verbose * WarningAction * WhatIf This function should be used in a module's function to grab the caller's preference variables so the caller doesn't have to explicitly pass common parameters to the module function. This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d). There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add explicit `-ErrorAction $ErrorActionPreference` to every function/cmdlet call in your function. Please vote up this issue so it can get fixed. .LINK about_Preference_Variables .LINK about_CommonParameters .LINK https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d .LINK http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/ .EXAMPLE Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Demonstrates how to set the caller's common parameter preference variables in a module function. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] #[Management.Automation.PSScriptCmdlet] # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` attribute. $Cmdlet, [Parameter(Mandatory = $true)] [Management.Automation.SessionState] # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the `[CmdletBinding()]` attribute. # # Used to set variables in its callers' scope, even if that caller is in a different script module. $SessionState ) Set-StrictMode -Version 'Latest' # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken from about_CommonParameters). $commonPreferences = @{ 'ErrorActionPreference' = 'ErrorAction'; 'DebugPreference' = 'Debug'; 'ConfirmPreference' = 'Confirm'; 'InformationPreference' = 'InformationAction'; 'VerbosePreference' = 'Verbose'; 'WarningPreference' = 'WarningAction'; 'WhatIfPreference' = 'WhatIf'; } foreach( $prefName in $commonPreferences.Keys ) { $parameterName = $commonPreferences[$prefName] # Don't do anything if the parameter was passed in. if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) ) { continue } $variable = $Cmdlet.SessionState.PSVariable.Get($prefName) # Don't do anything if caller didn't use a common parameter. if( -not $variable ) { continue } if( $SessionState -eq $ExecutionContext.SessionState ) { Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variable.Name, $variable.Value) } } } function Write-RivetError { param( [Parameter(Mandatory=$true)] [string] # The error message to display. $Message, [Parameter(Mandatory=$true)] [Exception] # The exception being reported. $Exception, [Parameter(Mandatory=$true)] [string] # The call stack to report. $CallStack, [Parameter(Mandatory=$true)] [string] # The Category Info $CategoryInfo, [Parameter(Mandatory=$true)] [string] # The Fully Qualified Error ID $ErrorID, [Parameter()] [string] # Query, if any $Query ) $firstException = $_.Exception while( $firstException.InnerException ) { $firstException = $firstException.InnerException } if (-not $Query) { $Query = "None" } Write-Error (@" [{0}].[{1}] {2}: {3} {4} QUERY ===== {5} "@ -f $Connection.DataSource,$Connection.Database,$Message,$firstException.Message,($CallStack -replace "`n","`n "), $Query) -ErrorID $ErrorID -Category $CategoryInfo } function New-BigIntColumn { <# .SYNOPSIS Creates a column object representing an BigInt datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Migrations' { BigInt 'MigrationID' } ## ALIASES * BigInt .EXAMPLE Add-Table 'Migrations' { BigInt 'MigrationID' } Demonstrates how to create an optional `bigint` column called `MigrationID`. .EXAMPLE Add-Table 'Migrations' { BigInt 'ID' -Identity 1 1 } Demonstrates how to create a required `bigint` column called `ID`, which is used as the table's identity. The identity values will start at 1, and increment by 1. .EXAMPLE Add-Table 'Migrations' { BigInt 'MigrationID' -NotNull } Demonstrates how to create a required `bigint` column called `MigrationID`. .EXAMPLE Add-Table 'Migrations' { BigInt 'MigrationID' -Sparse } Demonstrates how to create a sparse, optional `bigint` column called `MigrationID`. .EXAMPLE Add-Table 'Migrations' { BigInt 'MigrationID' -NotNull -Default '0' } Demonstrates how to create a required `bigint` column called `MigrationID` with a default value of `0`. .EXAMPLE Add-Table 'Migrations' { BigInt 'MigrationID' -NotNull -Description 'The number of items currently on hand.' } Demonstrates how to create a required `bigint` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='Identity')] [Parameter(Mandatory,ParameterSetName='IdentityWithSeed')] # The column should be an identity. [switch]$Identity, [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=1)] # The starting value for the identity. [int]$Seed, [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=2)] # The increment between auto-generated identity values. [int]$Increment, [Parameter(ParameterSetName='Identity')] [Parameter(ParameterSetName='IdentityWithSeed')] # Stops the identity from being replicated. [switch]$NotForReplication, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value. The DefaultConstraintName parameter is required if this parameter is used. [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) Set-StrictMode -Version 'Latest' switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::BigInt($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::BigInt($Name,'NotNull', $Default, $DefaultConstraintName, $Description) } 'Identity' { $i = New-Object 'Rivet.Identity' $NotForReplication [Rivet.Column]::BigInt( $Name, $i, $Description ) } 'IdentityWithSeed' { $i = New-Object 'Rivet.Identity' $Seed, $Increment, $NotForReplication [Rivet.Column]::BigInt( $Name, $i, $Description ) } } } Set-Alias -Name 'BigInt' -Value 'New-BigIntColumn' function New-BinaryColumn { <# .SYNOPSIS Creates a column object representing an Binary datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Images' { Binary 'Bits' 256 } ## ALIASES * Binary .EXAMPLE Add-Table 'Images' { Binary 'Bytes' 256 } Demonstrates how to create an optional `binary` column with a maximum length of 256 bytes. .EXAMPLE Add-Table 'Images' { Binary 'Bytes' 256 -NotNull } Demonstrates how to create a required `binary` column with maximum length of 256 bytes. .EXAMPLE Add-Table 'Images' { Binary 'Bytes' -Max } Demonstrates how to create an optional `binary` column with the maximum length (2^31 -1 bytes). .EXAMPLE Add-Table 'Images' { Binary 'Bytes' -Max -FileStream } Demonstrates now to create an optional `binary` column with the maximum length, and stores the data in a filestream data container. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,Position=1)] # The number of bytes the column will hold. [int]$Size, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) $sizetype = New-Object Rivet.CharacterLength $Size $nullable = 'Null' if( $PSCmdlet.ParameterSetName -eq 'NotNull' ) { $nullable = 'NotNull' } elseif( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::Binary($Name, $sizetype, $nullable, $Default, $DefaultConstraintName, $Description) } Set-Alias -Name 'Binary' -Value 'New-BinaryColumn' function New-BitColumn { <# .SYNOPSIS Creates a column object representing an Bit datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Items' { Bit 'IsAvailable' } ## ALIASES * Bit .EXAMPLE Add-Table 'Items' { Bit 'IsAvailable' } Demonstrates how to create an optional `bit` column called `IsAvailable`. .EXAMPLE Add-Table 'Items' { Bit 'IsAvailable' -NotNull } Demonstrates how to create a required `bit` column called `IsAvailable`. .EXAMPLE Add-Table 'Items' { Bit 'IsAvailable' -Sparse } Demonstrates how to create a sparse, optional `bit` column called `IsAvailable`. .EXAMPLE Add-Table 'Items' { Bit 'IsAvailable' -NotNull -Default '1' } Demonstrates how to create a required `bit` column called `IsAvailable` with a default value of `1`. .EXAMPLE Add-Table 'Items' { Bit 'IsAvailable' -NotNull -Description 'The price of the item.' } Demonstrates how to create a required `bit` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::Bit($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::Bit($Name,'NotNull', $Default, $DefaultConstraintName, $Description) } } } Set-Alias -Name 'Bit' -Value 'New-BitColumn' function New-CharColumn { <# .SYNOPSIS Creates a column object representing an Char datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table -State 'Addresses' -Column { Char 'State' 2 } ## ALIASES * Char .EXAMPLE Add-Table 'Addresses' { Char 'State' 2 } Demonstrates how to create an optional `char` column with a length of 2 bytes. .EXAMPLE Add-Table 'Addresses' { Char 'State' 2 -NotNull } Demonstrates how to create a required `char` column with length of 2 bytes. .EXAMPLE Add-Table 'Addresses' { Char 'State' 2 -Collation 'Latin1_General_BIN' } Demonstrates now to create an optional `char` column with a custom `Latin1_General_BIN` collation. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,Position=1)] [Alias('Length')] # The length of the column, i.e. the number of characters. [int]$Size, # Controls the code page that is used to store the data [String]$Collation, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) $Sizetype = $null $Sizetype = New-Object Rivet.CharacterLength $Size $nullable = 'Null' if( $PSCmdlet.ParameterSetName -eq 'NotNull' ) { $nullable = 'NotNull' } elseif( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::Char($Name, $Sizetype, $Collation, $nullable, $Default, $DefaultConstraintName, $Description) } Set-Alias -Name 'Char' -Value 'New-CharColumn' function New-Column { <# .SYNOPSIS Creates a column object of an explicit datatype which can be used with the `Add-Table` or `Update-Table` migrations. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Members' { New-Column 'Birthday' 'datetime' } This column is useful for creating columns of custom types or types for which Rivet doesn't have a specific function. Returns an object that can be used when adding columns or creating tables to get the SQL needed to create that column. .LINK New-BigIntColumn .LINK New-BinaryColumn .LINK New-BitColumn .LINK New-CharColumn .LINK New-DateColumn .LINK New-DateTime2Column .LINK New-DateTimeOffsetColumn .LINK New-DecimalColumn .LINK New-FloatColumn .LINK New-HierarchyIDColumn .LINK New-IntColumn .LINK New-MoneyColumn .LINK New-NCharColumn .LINK New-NVarCharColumn .LINK New-RealColumn .LINK New-RowVersionColumn .LINK New-SmallDateTimeColumn .LINK New-SmallIntColumn .LINK New-SmallMoneyColumn .LINK New-SqlVariantColumn .LINK New-TimeColumn .LINK New-TinyIntColumn .LINK New-UniqueIdentifierColumn .LINK New-VarBinaryColumn .LINK New-VarCharColumn .LINK New-XmlColumn .EXAMPLE Add-Table 'Members' { New-Column 'Birthday' 'datetime' -NotNull } Demonstrates how to create a required `datetime` column. .EXAMPLE Add-Table 'Members' { New-Column 'Birthday' 'float(7)' -NotNull } Demonstrates that the value of the `DataType` parameter should also include any precision/scale/size specifiers. .EXAMPLE Add-Table 'Members' { New-Column 'Birthday' 'datetime' -Sparse } Demonstrate show to create a nullable, sparse `datetime` column when adding a new table. .EXAMPLE Add-Table 'Members' { New-Column 'Birthday' 'datetime' -NotNull -Default 'getdate()' } Demonstrates how to create a date column with a default value, in this case the current date. (You alwyas use UTC dates, right?) Probably not a great example, setting someone's birthday to the current date. Reasons are left as an exercise for the reader. .EXAMPLE Add-Table 'Members' { New-Column 'Birthday' 'datetime' -Description 'The members birthday.' } Demonstrates how to create an optional date column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The Name of the new column. [String]$Name, [Parameter(Mandatory,Position=1)] # The datatype of the new column. Scale/size/precision clause is optional. [String]$DataType, [Parameter(ParameterSetName='Nullable')] [Parameter(ParameterSetName='NotNull')] # Allow the column to be its maximum size. Sets the columnn's size clause to `(max)`. Only use this with columns whose underlying type supports it. If you supply this argument, the `Size`, `Precision`, and `Scale` parameters are ignored. [switch]$Max, [Parameter(ParameterSetName='Nullable')] [Parameter(ParameterSetName='NotNull')] # The size/length of the column. Sets the column's size clause to `($Size)`. Ignored if `Max` parameter is used. If provided, the `Precision` and `Scale` parameters are ignored. [int]$Size, [Parameter(ParameterSetName='Nullable')] [Parameter(ParameterSetName='NotNull')] # The precision of the column. Set's the columns size clause to `($Precision)`. If `Scale` is also given, the size clause is set to `($Precision,$Scale)`. Ignored if the `Max` or `Size` parameters are used. [int]$Precision, [Parameter(ParameterSetName='Nullable')] [Parameter(ParameterSetName='NotNull')] # The scale of the column. Set's the column's size clause to `($Scale)`. If `Precision` is also given, the size clause is set to `($Precision,$Scale)`. Ignored if the `Max` or `Size` parameters are used. [int]$Scale, [Parameter(Mandatory,ParameterSetName='Identity')] # Make the column an identity. [switch]$Identity, [Parameter(ParameterSetName='Identity')] # The starting value for the identity column. [int]$Seed, [Parameter(ParameterSetName='Identity')] # The increment between new identity values. [int]$Increment, [Parameter(ParameterSetName='Identity')] # Don't replicate the identity column value. [switch]$NotForReplication, [Parameter(ParameterSetName='Nullable')] # Optimizes the column storage for null values. Cannot be used with the `NotNull` switch. [switch]$Sparse, [Parameter(ParameterSetName='NotNull')] # Makes the column not nullable. Cannot be used with the `Sparse` switch. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] [Parameter(ParameterSetName='NotNull')] # The collation of the column. [String]$Collation, # Whether or not to make the column a `rowguidcol`. [switch]$RowGuidCol, [Parameter(ParameterSetName='Nullable')] [Parameter(ParameterSetName='NotNull')] # A SQL Server expression for the column's default value. [Object]$Default, [Parameter(ParameterSetName='Nullable')] [Parameter(ParameterSetName='NotNull')] # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description, # Whether or not the column is a filestream. [switch]$FileStream ) [Rivet.ColumnSize]$sizeParam = $null if( $Max ) { $sizeParam = [Rivet.CharacterLength]::new() } elseif( $PSBoundParameters.ContainsKey('Size') ) { $sizeParam = [Rivet.CharacterLength]::new($Size) } elseif( $PSBoundParameters.ContainsKey('Precision') -and $PSBoundParameters.ContainsKey('Scale') ) { $sizeParam = [Rivet.PrecisionScale]::new($Precision,$Scale) } elseif( $PSBoundParameters.ContainsKey('Precision') ) { $sizeParam = [Rivet.PrecisionScale]::new($Precision) } elseif( $PSBoundParameters.ContainsKey('Scale') ) { $sizeParam = [Rivet.Scale]::new($Scale) } if( $PSCmdlet.ParameterSetName -eq 'Identity' ) { [Rivet.Identity]$identityParam = [Rivet.Identity]::new($NotForReplication) if( $Seed -or $Increment ) { $identityParam = [Rivet.Identity]::new($Seed, $Increment, $NotForReplication) } [Rivet.Column]::new($Name, $DataType, $sizeParam, $identityParam, $RowGuidCol, $Description, $FileStream) } else { $nullable = 'Null' if( $PSCmdlet.ParameterSetName -eq 'NotNull' ) { $nullable = 'NotNull' } elseif( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::new($Name, $DataType, $sizeParam, $nullable, $Collation, $RowGuidCol, $Default, $DefaultConstraintName, $Description, $FileStream) } } function New-DateColumn { <# .SYNOPSIS Creates a column object representing an Date datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Members' { Date 'Birthday' } ## ALIASES * Date .EXAMPLE Add-Table 'Members' { New-DateColumn 'Birthday' -NotNull } Demonstrates how to create a required `date` column. .EXAMPLE Add-Table 'Members' { Date 'Birthday' -Sparse } Demonstrate show to create a nullable, sparse `date` column when adding a new table. .EXAMPLE Add-Table 'Members' { Date 'Birthday' -NotNull -Default 'get`date`()' } Demonstrates how to create a `date` column with a default value, in this case the current `date`. (You alwyas use UTC `date`s, right?) Probably not a great example, setting someone's birthday to the current `date`. Reasons are left as an exercise for the reader. .EXAMPLE Add-Table 'Members' { Date 'Birthday' -Description 'The members birthday.' } Demonstrates how to create an optional `date` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::Date($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::Date($Name, [Rivet.Nullable]::NotNull, $Default, $DefaultConstraintName, $Description) } } } Set-Alias -Name 'Date' -Value 'New-DateColumn' function New-DateTime2Column { <# .SYNOPSIS Creates a column object representing an DateTime2 datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Orders' { DateTime2 'OrderedAt' } ## ALIASES * DateTime2 .EXAMPLE Add-Table 'Orers' { DateTime2 'OrderedAt' } Demonstrates how to create an optional `datetime2` column. .EXAMPLE Add-Table 'Orders' { DateTime2 'OrderedAt' 5 -NotNull } Demonstrates how to create a required `datetime2` column with 5 digits of fractional seconds precision. .EXAMPLE Add-Table 'Orders' { DateTime2 'OrderedAt' -Sparse } Demonstrate show to create a nullable, sparse `datetime2` column when adding a new table. .EXAMPLE Add-Table 'Orders' { DateTime2 'OrderedAt' -NotNull -Default 'getutcdate()' } Demonstrates how to create a `datetime2` column with a default value. You only use UTC dates, right? .EXAMPLE Add-Table 'Orders' { DateTime2 'OrderedAt' -NotNull -Description 'The time the record was created.' } Demonstrates how to create a `datetime2` column with a description. #> [CmdletBinding(DefaultParameterSetName='Null')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Position=1)] [Alias('Precision')] # The number of decimal digits for the fractional seconds. SQL Server's default is `7`, or 100 nanoseconds. [int]$Scale, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Null')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) $dataSize = $null if( $PSBoundParameters.ContainsKey( 'Scale' ) ) { $dataSize = New-Object Rivet.Scale $Scale } $nullable = $PSCmdlet.ParameterSetName if( $nullable -eq 'Null' -and $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::DateTime2($Name, $dataSize, $nullable, $Default, $DefaultConstraintName, $Description) } Set-Alias -Name 'DateTime2' -Value 'New-DateTime2Column' function New-DateTimeColumn { <# .SYNOPSIS Creates a column object representing an DateTime datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Orders' { DateTime 'OrderedAt' } ## ALIASES * DateTime .EXAMPLE Add-Table 'Orers' { DateTime 'OrderedAt' } Demonstrates how to create an optional `datetime` column. .EXAMPLE Add-Table 'Orders' { DateTime 'OrderedAt' 5 -NotNull } Demonstrates how to create a required `datetime` column with 5 digits of fractional seconds precision. .EXAMPLE Add-Table 'Orders' { DateTime 'OrderedAt' -Sparse } Demonstrate show to create a nullable, sparse `datetime` column when adding a new table. .EXAMPLE Add-Table 'Orders' { DateTime 'OrderedAt' -NotNull -Default 'getutcdate()' } Demonstrates how to create a `datetime` column with a default value. You only use UTC dates, right? .EXAMPLE Add-Table 'Orders' { DateTime 'OrderedAt' -NotNull -Description 'The time the record was created.' } Demonstrates how to create a `datetime` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) if ($PsCmdlet.ParameterSetName -eq 'Nullable') { if ($Sparse) { New-Column -Name $Name -DataType 'datetime' -Sparse -Default $Default -DefaultConstraintName $DefaultConstraintName -Description $Description } else { New-Column -Name $Name -DataType 'datetime' -Default $Default -DefaultConstraintName $DefaultConstraintName -Description $Description } } elseif ($PsCmdlet.ParameterSetName -eq 'NotNull') { New-Column -Name $Name -DataType 'datetime' -NotNull -Default $Default -DefaultConstraintName $DefaultConstraintName -Description $Description } } Set-Alias -Name 'DateTime' -Value 'New-DateTimeColumn' function New-DateTimeOffsetColumn { <# .SYNOPSIS Creates a column object representing an DateTimeOffset datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Orders' { DateTimeOffset 'OrderedAt' } ## ALIASES * DateTimeOffset .EXAMPLE Add-Table 'Orers' { DateTimeOffset 'OrderedAt' } Demonstrates how to create an optional `datetimeoffset` column. .EXAMPLE Add-Table 'Orders' { DateTimeOffset 'OrderedAt' 5 -NotNull } Demonstrates how to create a required `datetimeoffset` column with a digits of fractional seconds precision. .EXAMPLE Add-Table 'Orders' { DateTimeOffset 'OrderedAt' -Sparse } Demonstrate show to create a nullable, sparse `datetimeoffset` column when adding a new table. .EXAMPLE Add-Table 'Orders' { DateTimeOffset 'OrderedAt' -NotNull -Default 'getutcdate()' } Demonstrates how to create a `datetimeoffset` column with a default value. You only use UTC dates, right? .EXAMPLE Add-Table 'Orders' { DateTimeOffset 'OrderedAt' -NotNull -Description 'The time the record was created.' } Demonstrates how to create a `datetimeoffset` column with a description. #> [CmdletBinding(DefaultParameterSetName='Null')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Position=1)] [Alias('Precision')] # The number of decimal digits for the fractional seconds. SQL Server's default is `7`, or 100 nanoseconds. [int]$Scale, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Null')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) $dataSize = $null if( $PSBoundParameters.ContainsKey('Scale') ) { $dataSize = New-Object Rivet.Scale $Scale } $nullable = $PSCmdlet.ParameterSetName if( $nullable -eq 'Null' -and $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::DateTimeOffset($Name, $dataSize, $nullable, $Default, $DefaultConstraintName, $Description) } Set-Alias -Name 'DateTimeOffset' -Value 'New-DateTimeOffsetColumn' function New-DecimalColumn { <# .SYNOPSIS Creates a column object representing a `decimal` data type. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Items' { Decimal 'Price' } ## ALIASES * Decimal * Numeric * New-NumericColumn .EXAMPLE Add-Table 'Items' { Decimal 'Price' 5 2 } Demonstrates how to create an optional `decimal` column called `Price`, with a five-digit precision (prices less than $999.99) and a scale of 2 (2 digits after the `decimal`). .EXAMPLE Add-Table 'Items' { Decimal 'Price' -Identity -Seed 1 -Increment 1 } Demonstrates how to create a required `decimal` column called `Price`, which is used as the table's identity. The identity values will start at 1, and increment by 1. Uses SQL Server's default precision/scale. .EXAMPLE Add-Table 'Items' { Decimal 'Price' -NotNull } Demonstrates how to create a required `decimal` column called `Price`. Uses SQL Server's default precision/scale. .EXAMPLE Add-Table 'Items' { Decimal 'Price' -Sparse } Demonstrates how to create a sparse, optional `decimal` column called `Price`. Uses SQL Server's default precision/scale. .EXAMPLE Add-Table 'Items' { Decimal 'Price' -NotNull -Default '0' } Demonstrates how to create a required `decimal` column called `Price` with a default value of `0`. Uses SQL Server's default precision/scale. .EXAMPLE Add-Table 'Items' { Decimal 'Price' -NotNull -Description 'The price of the item.' } Demonstrates how to create a required `decimal` column with a description. Uses SQL Server's default precision/scale. #> [CmdletBinding(DefaultParameterSetName='Null')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Position=1)] # Maximum total number of decimal digits that will be stored. [int]$Precision, [Parameter(Position=2)] # The number of decimal digits that will be stored to the right of the decimal point. [int]$Scale, [Parameter(Mandatory,ParameterSetName='Identity')] # The column should be an identity. [switch]$Identity, [Parameter(ParameterSetName='Identity')] # The starting value for the identity. [int]$Seed, [Parameter(ParameterSetName='Identity')] # The increment between auto-generated identity values. [int]$Increment, [Parameter(ParameterSetName='Identity')] # Stops the identity from being replicated. [switch]$NotForReplication, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Null')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) $dataSize = $null if( $PSBoundParameters.ContainsKey( 'Precision' ) -and $PSBoundParameters.ContainsKey( 'Scale' ) ) { $dataSize = New-Object Rivet.PrecisionScale $Precision, $Scale } elseif( $PSBoundParameters.ContainsKey( 'Precision' ) ) { $dataSize = New-Object Rivet.PrecisionScale $Precision } elseif( $PSBoundParameters.ContainsKey( 'Scale' ) ) { throw ('New-DecimalColumn: a scale for column {0} is given, but no precision. Please remove the `-Scale` parameter, or add a `-Precision` parameter with a value.' -f $Name) } switch ($PSCmdlet.ParameterSetName) { 'Null' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::Decimal($Name, $dataSize, $nullable, $Default, $DefaultConstraintName, $Description) break } 'NotNull' { [Rivet.Column]::Decimal($Name, $dataSize, 'NotNull', $Default, $DefaultConstraintName, $Description) break } 'Identity' { if( $PSBoundParameters.ContainsKey('Seed') -and $PSBoundParameters.ContainsKey('Increment') ) { $i = New-Object 'Rivet.Identity' $Seed,$Increment,$NotForReplication } elseif( $PSBoundParameters.ContainsKey('Seed') ) { $i = New-Object 'Rivet.Identity' $Seed,1,$NotForReplication } elseif( $PSBoundParameters.ContainsKey('Increment') ) { $i = New-Object 'Rivet.Identity' 1,$Increment,$NotForReplication } else { $i = New-Object 'Rivet.Identity' $NotForReplication } [Rivet.Column]::Decimal( $Name, $dataSize, $i, $Description ) break } } } Set-Alias -Name 'Decimal' -Value 'New-DecimalColumn' Set-Alias -Name 'Numeric' -Value 'New-DecimalColumn' Set-Alias -Name 'New-NumericColumn' -Value 'New-DecimalColumn' function New-FloatColumn { <# .SYNOPSIS Creates a column object representing a `float` datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Items' { Float 'Price' } ## ALIASES * Float .EXAMPLE Add-Table 'Items' { Float 'Price' -Precision 5 } Demonstrates how to create an optional `float` column called `Price`, with a precision of 5. .EXAMPLE Add-Table 'Items' { Float 'Price' -NotNull } Demonstrates how to create a required `float` column called `Price`. Uses SQL Server's default precision. .EXAMPLE Add-Table 'Items' { Float 'Price' -Sparse } Demonstrates how to create a sparse, optional `float` column called `Price`. Uses SQL Server's default precision. .EXAMPLE Add-Table 'Items' { Float 'Price' -NotNull -Default '0.0' } Demonstrates how to create a required `float` column called `Price` with a default value of `0`. Uses SQL Server's default precision. .EXAMPLE Add-Table 'Items' { Float 'Price' -NotNull -Description 'The price of the item.' } Demonstrates how to create a required `float` column with a description. Uses SQL Server's default precision. #> [CmdletBinding(DefaultParameterSetName='Null')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Position=1)] # Maximum total number of Numeric digits that will be stored [int]$Precision, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Null')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) $dataSize = $null if ($Precision -gt 0) { $dataSize = New-Object Rivet.PrecisionScale $Precision } $nullable = $PSCmdlet.ParameterSetName if( $nullable -eq 'Null' -and $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::Float($Name, $dataSize, $nullable, $Default, $DefaultConstraintName, $Description) } Set-Alias -Name 'Float' -Value 'New-FloatColumn' function New-HierarchyIDColumn { <# .SYNOPSIS Creates a column object representing an HierarchyID datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'FamilyTree' { HierarchyID 'Father' } ## ALIASES * HierarchyID .EXAMPLE Add-Table 'FamilyTree' { HierarchyID 'Father' } Demonstrates how to create an optional `hierarchyid` column called `Father`. .EXAMPLE Add-Table 'FamilyTree' { HierarchyID 'Father' -NotNull } Demonstrates how to create a required `hierarchyid` column called `Father`. .EXAMPLE Add-Table 'FamilyTree' { HierarchyID 'Father' -Sparse } Demonstrates how to create a sparse, optional `hierarchyid` column called `Father`. .EXAMPLE Add-Table 'FamilyTree' { HierarchyID 'Father' -NotNull -Description "The hierarchy ID of this person's father." } Demonstrates how to create a required `hierarchyid` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::HierarchyID($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::HierarchyID($Name,'NotNull', $Default, $DefaultConstraintName, $Description) } } } Set-Alias -Name 'HierarchyID' -Value 'New-HierarchyIDColumn' function New-IntColumn { <# .SYNOPSIS Creates a column object representing an Int datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Items' { Int 'Quantity' } ## ALIASES * Int .EXAMPLE Add-Table 'Items' { Int 'Quantity' } Demonstrates how to create an optional `int` column called `Quantity`. .EXAMPLE Add-Table 'Items' { Int 'Quantity' -Identity 1 1 } Demonstrates how to create a required `int` column called `Quantity`, which is used as the table's identity. The identity values will start at 1, and increment by 1. .EXAMPLE Add-Table 'Items' { Int 'Quantity' -NotNull } Demonstrates how to create a required `int` column called `Quantity`. .EXAMPLE Add-Table 'Items' { Int 'Quantity' -Sparse } Demonstrates how to create a sparse, optional `int` column called `Quantity`. .EXAMPLE Add-Table 'Items' { Int 'Quantity' -NotNull -Default '0' } Demonstrates how to create a required `int` column called `Quantity` with a default value of `0`. .EXAMPLE Add-Table 'Items' { Int 'Quantity' -NotNull -Description 'The number of items currently on hand.' } Demonstrates how to create a required `int` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='Identity')] [Parameter(Mandatory,ParameterSetName='IdentityWithSeed')] # The column should be an identity. [switch]$Identity, [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=1)] # The starting value for the identity. [int]$Seed, [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=2)] # The increment between auto-generated identity values. [int]$Increment, [Parameter(ParameterSetName='Identity')] [Parameter(ParameterSetName='IdentityWithSeed')] # Stops the identity from being replicated. [switch]$NotForReplication, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::Int($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::Int($Name,'NotNull', $Default, $DefaultConstraintName, $Description) } 'Identity' { $i = New-Object 'Rivet.Identity' $NotForReplication [Rivet.Column]::Int( $Name, $i, $Description ) } 'IdentityWithSeed' { $i = New-Object 'Rivet.Identity' $Seed, $Increment, $NotForReplication [Rivet.Column]::Int( $Name, $i, $Description ) } } } Set-Alias -Name 'Int' -Value 'New-IntColumn' function New-MoneyColumn { <# .SYNOPSIS Creates a column object representing an Money datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Items' { Money 'Price' } ## ALIASES * Money .EXAMPLE Add-Table 'Items' { Money 'Price' } Demonstrates how to create an optional `money` column called `Price`. .EXAMPLE Add-Table 'Items' { Money 'Price' -NotNull } Demonstrates how to create a required `money` column called `Price`. .EXAMPLE Add-Table 'Items' { Money 'Price' -Sparse } Demonstrates how to create a sparse, optional `money` column called `Price`. .EXAMPLE Add-Table 'Items' { Money 'Price' -NotNull -Default '0.00' } Demonstrates how to create a required `money` column called `Price` with a default value of `$0.00`. .EXAMPLE Add-Table 'Items' { Money 'Price' -NotNull -Description 'The price of the item.' } Demonstrates how to create a required `money` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::Money($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::Money($Name,'NotNull', $Default, $DefaultConstraintName, $Description) } } } Set-Alias -Name 'Money' -Value 'New-MoneyColumn' function New-NCharColumn { <# .SYNOPSIS Creates a column object representing an NChar datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table -State 'Addresses' -Column { NChar 'State' 2 } ## ALIASES * NChar .EXAMPLE Add-Table 'Addresses' { NChar 'State' 2 } Demonstrates how to create an optional `nchar` column with a length of 2 bytes. .EXAMPLE Add-Table 'Addresses' { NChar 'State' 2 -NotNull } Demonstrates how to create a required `nchar` column with length of 2 bytes. .EXAMPLE Add-Table 'Addresses' { NChar 'State' 2 -Collation 'Latin1_General_BIN' } Demonstrates now to create an optional `nchar` column with a custom `Latin1_General_BIN` collation. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,Position=1)] # Defines the string Size of the fixed-Size string data. Default is 30 [int]$Size, # Controls the code page that is used to store the data [String]$Collation, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) $Sizetype = $null $Sizetype = New-Object Rivet.CharacterLength $Size $nullable = 'Null' if( $PSCmdlet.ParameterSetName -eq 'NotNull' ) { $nullable = 'NotNull' } elseif( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::NChar($Name, $Sizetype, $Collation, $nullable, $Default, $DefaultConstraintName, $Description) } Set-Alias -Name 'NChar' -Value 'New-NCharColumn' function New-NVarCharColumn { <# .SYNOPSIS Creates a column object representing an NVarChar datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table -Name 'Albums' -Column { NVarChar 'Name' 50 } ## ALIASES * NVarChar .EXAMPLE Add-Table 'Albums' { NVarChar 'Name' 100 } Demonstrates how to create an optional `nvarchar` column with a maximum length of 100 bytes. .EXAMPLE Add-Table 'Albums' { NVarChar 'Name' 100 -NotNull } Demonstrates how to create a required `nvarchar` column with maximum length of 100 bytes. .EXAMPLE Add-Table 'Albums' { NVarChar 'Name' -Max } Demonstrates how to create an optional `nvarchar` column with the maximum length (about 2GB). .EXAMPLE Add-Table 'Albums' { NVarChar 'Name' 100 -Collation 'Latin1_General_BIN' } Demonstrates now to create an optional `nvarchar` column with a custom `Latin1_General_BIN` collation. #> [CmdletBinding(DefaultParameterSetName='NullSize')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,Position=1,ParameterSetName='NullSize')] [Parameter(Mandatory,Position=1,ParameterSetName='NotNullSize')] [Alias('Length')] # The maximum length of the column, i.e. the number of unicode characters. [int]$Size, [Parameter(Mandatory,ParameterSetName='NullMax')] [Parameter(Mandatory,ParameterSetName='NotNullMax')] # Create an `nvarchar(max)` column. [switch]$Max, # Controls the code page that is used to store the data [String]$Collation, [Parameter(Mandatory,ParameterSetName='NotNullSize')] [Parameter(Mandatory,ParameterSetName='NotNullMax')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='NullSize')] [Parameter(ParameterSetName='NullMax')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) $sizeType = $null if( $PSCmdlet.ParameterSetName -like '*Size' ) { $sizeType = New-Object Rivet.CharacterLength $Size } else { $sizeType = New-Object Rivet.CharacterLength @() } $nullable = 'Null' if( $PSCmdlet.ParameterSetName -like 'NotNull*' ) { $nullable = 'NotNull' } elseif( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::NVarChar($Name, $sizeType, $Collation, $nullable, $Default, $DefaultConstraintName, $Description) } Set-Alias -Name 'NVarChar' -Value 'New-NVarCharColumn' function New-RealColumn { <# .SYNOPSIS Creates a column object representing an Real datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Items' { Real 'Price' } ## ALIASES * Real .EXAMPLE Add-Table 'Items' { Real 'Price' } Demonstrates how to create an optional `real` column called `Price`. .EXAMPLE Add-Table 'Items' { Real 'Price' -NotNull } Demonstrates how to create a required `real` column called `Price`. .EXAMPLE Add-Table 'Items' { Real 'Price' -Sparse } Demonstrates how to create a sparse, optional `real` column called `Price`. .EXAMPLE Add-Table 'Items' { Real 'Price' -NotNull -Default '0.00' } Demonstrates how to create a required `real` column called `Price` with a default value of `$0.00`. .EXAMPLE Add-Table 'Items' { Real 'Price' -NotNull -Description 'The price of the item.' } Demonstrates how to create a required `real` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::Real($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::Real($Name,'NotNull', $Default, $DefaultConstraintName, $Description) } } } Set-Alias -Name 'Real' -Value 'New-RealColumn' function New-RowVersionColumn { <# .SYNOPSIS Creates a column object representing an RowVersion datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'WithUUID' { RowVersion 'ColumnName' } ## ALIASES * RowVersion .EXAMPLE Add-Table Changes { RowVersion 'Version' } Demonstrates how to create a table with an optional `rowversion` column. .EXAMPLE Add-Table Locations { RowVersion 'LocationID' -RowGuidCol } Demonstrates how to create a table with an optional `rowversion`, which is used as the RowGuid identifier for SQL Server replication. .EXAMPLE Add-Table Locations { RowVersion 'LocationID' -NotNull } Demonstrates how to create a table with an required `rowversion` column. .EXAMPLE Add-Table Locations { RowVersion 'LocationID' -Default 'newid()' } Demonstrates how to create a table with an optional `rowversion` column with a default value. .EXAMPLE Add-Table Locations { RowVersion 'LocationID' -Description 'The unique identifier for this location.' } Demonstrates how to create a table with an optional `rowversion` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::RowVersion($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::RowVersion($Name,'NotNull', $Default, $DefaultConstraintName, $Description) } } } Set-Alias -Name 'RowVersion' -Value 'New-RowVersionColumn' function New-SmallDateTimeColumn { <# .SYNOPSIS Creates a column object representing an SmallDateTime datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Orders' { SmallDateTime 'OrderedAt' } ## ALIASES * SmallDateTime .EXAMPLE Add-Table 'Orders' { New-SmallDateTimeColumn 'OrderedAt' -NotNull } Demonstrates how to create a required `smalldatetime` colum when adding a new table. .EXAMPLE Add-Table 'Orders' { SmallDateTime 'OrderedAt' -Sparse } Demonstrate show to create a nullable, sparse `smalldatetime` column when adding a new table. .EXAMPLE Add-Table 'Orders' { SmallDateTime 'OrderedAt' -NotNull -Default 'getutcdate()' } Demonstrates how to create a `smalldatetime` column with a default value. You only use UTC dates, right? .EXAMPLE Add-Table 'Orders' { SmallDateTime 'OrderedAt' -NotNull -Description 'The time the record was created.' } Demonstrates how to create a `smalldatetime` column a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::SmallDateTime($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::SmallDateTime($Name,'NotNull', $Default, $DefaultConstraintName, $Description) } } } Set-Alias -Name 'SmallDateTime' -Value 'New-SmallDateTimeColumn' function New-SmallIntColumn { <# .SYNOPSIS Creates a column object representing an SmallInt datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Items' { SmallInt 'Quantity' } ## ALIASES * SmallInt .EXAMPLE Add-Table 'Items' { SmallInt 'Quantity' } Demonstrates how to create an optional `smallint` column called `Quantity`. .EXAMPLE Add-Table 'Items' { SmallInt 'Quantity' -Identity 1 1 } Demonstrates how to create a required `smallint` column called `Quantity`, which is used as the table's identity. The identity values will start at 1, and increment by 1. .EXAMPLE Add-Table 'Items' { SmallInt 'Quantity' -NotNull } Demonstrates how to create a required `smallint` column called `Quantity`. .EXAMPLE Add-Table 'Items' { SmallInt 'Quantity' -Sparse } Demonstrates how to create a sparse, optional `smallint` column called `Quantity`. .EXAMPLE Add-Table 'Items' { SmallInt 'Quantity' -NotNull -Default '0' } Demonstrates how to create a required `smallint` column called `Quantity` with a default value of `0`. .EXAMPLE Add-Table 'Items' { SmallInt 'Quantity' -NotNull -Description 'The number of items currently on hand.' } Demonstrates how to create a required `smallint` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='Identity')] [Parameter(Mandatory,ParameterSetName='IdentityWithSeed')] # The column should be an identity. [switch]$Identity, [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=1)] # The starting value for the identity. [int]$Seed, [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=2)] # The increment between auto-generated identity values. [int]$Increment, [Parameter(ParameterSetName='Identity')] [Parameter(ParameterSetName='IdentityWithSeed')] # Stops the identity from being replicated. [switch]$NotForReplication, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::SmallInt($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::SmallInt($Name,'NotNull', $Default, $DefaultConstraintName, $Description) } 'Identity' { $i = New-Object 'Rivet.Identity' $NotForReplication [Rivet.Column]::SmallInt( $Name, $i, $Description ) } 'IdentityWithSeed' { $i = New-Object 'Rivet.Identity' $Seed, $Increment, $NotForReplication [Rivet.Column]::SmallInt( $Name, $i, $Description ) } } } Set-Alias -Name 'SmallInt' -Value 'New-SmallIntColumn' function New-SmallMoneyColumn { <# .SYNOPSIS Creates a column object representing an SmallMoney datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Items' { SmallMoney 'Price' } ## ALIASES * SmallMoney .EXAMPLE Add-Table 'Items' { SmallMoney 'Price' } Demonstrates how to create an optional `smallmoney` column called `Price`. .EXAMPLE Add-Table 'Items' { SmallMoney 'Price' -NotNull } Demonstrates how to create a required `smallmoney` column called `Price`. .EXAMPLE Add-Table 'Items' { SmallMoney 'Price' -Sparse } Demonstrates how to create a sparse, optional `smallmoney` column called `Price`. .EXAMPLE Add-Table 'Items' { SmallMoney 'Price' -NotNull -Default '0.00' } Demonstrates how to create a required `smallmoney` column called `Price` with a default value of `$0.00`. .EXAMPLE Add-Table 'Items' { SmallMoney 'Price' -NotNull -Description 'The price of the item.' } Demonstrates how to create a required `smallmoney` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::SmallMoney($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::SmallMoney($Name,'NotNull', $Default, $DefaultConstraintName, $Description) } } } Set-Alias -Name 'SmallMoney' -Value 'New-SmallMoneyColumn' function New-SqlVariantColumn { <# .SYNOPSIS Creates a column object representing an SqlVariant datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'WithSqlVariant' { SqlVariant 'ColumnName' } ## ALIASES * SqlVariant .EXAMPLE Add-Table 'WithSqlVar' { SqlVariant 'WhoKnows' } Demonstrates how to create an optional `sql_variant` column called `WhoKnows`. .EXAMPLE Add-Table 'WithSqlVar' { SqlVariant 'WhoKnows' -NotNull } Demonstrates how to create a required `sql_variant` column called `WhoKnows`. .EXAMPLE Add-Table 'WithSqlVar' { SqlVariant 'WhoKnows' -Sparse } Demonstrates how to create a sparse, optional `sql_variant` column called `WhoKnows`. .EXAMPLE Add-Table 'WithSqlVar' { SqlVariant 'WhoKnows' -NotNull -Default '1' } Demonstrates how to create a required `sql_variant` column called `WhoKnows` with a default value of `1`. .EXAMPLE Add-Table 'WithSqlVar' { SqlVariant 'WhoKnows' -NotNull -Description 'The contents of this column are left as an exercise for the reader.' } Demonstrates how to create a required `sql_variant` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::SqlVariant($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::SqlVariant($Name,'NotNull', $Default, $DefaultConstraintName, $Description) } } } Set-Alias -Name 'SqlVariant' -Value 'New-SqlVariantColumn' function New-TimeColumn { <# .SYNOPSIS Creates a column object representing an Time datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'WithTime' { Time 'ColumnName' } ## ALIASES * Time .EXAMPLE Add-Table 'WithTime' { New-TimeColumn 'CreatedAt' 5 -NotNull } Demonstrates how to create a required `time` column with a given scale when adding a new table. .EXAMPLE Add-Table 'WithTime' { Time 'CreatedAt' -Sparse } Demonstrate show to create a nullable, sparse `time` column when adding a new table. .EXAMPLE Add-Table 'WithTime' { Time 'CreatedAt' -NotNull -Default 'convert(`time`, getutcdate())' } Demonstrates how to create a `time` column with a default value, in this case the current time. You alwyas use UTC, right? .EXAMPLE Add-Table 'WithTime' { Time 'CreatedAt' -NotNull -Description 'The `time` the record was created.' } Demonstrates how to create a `time` column with a description. #> [CmdletBinding(DefaultParameterSetName='Null')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Position=1)] [Alias('Precision')] # The number of decimal digits for the fractional seconds. SQL Server's default is `7`, or 100 nanoseconds.. [int]$Scale, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Null')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) $dataSize = $null if( $PSBoundParameters.ContainsKey('Scale') ) { $dataSize = New-Object Rivet.Scale $Scale } $nullable = $PSCmdlet.ParameterSetName if( $nullable -eq 'Null' -and $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::Time($Name, $dataSize, $nullable, $Default, $DefaultConstraintName, $Description) } Set-Alias -Name 'Time' -Value 'New-TimeColumn' function New-TinyIntColumn { <# .SYNOPSIS Creates a column object representing an TinyInt datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'WithTintyInt' { TinyInt 'ColumnName' } ## ALIASES * TinyInt .EXAMPLE Add-Table 'Items' { TinyInt 'Quantity' } Demonstrates how to create an optional `tinyint` column called `Quantity`. .EXAMPLE Add-Table 'Items' { TinyInt 'Quantity' -Identity 1 1 } Demonstrates how to create a required `tinyint` column called `Quantity`, which is used as the table's identity. The identity values will start at 1, and increment by 1. .EXAMPLE Add-Table 'Items' { TinyInt 'Quantity' -NotNull } Demonstrates how to create a required `tinyint` column called `Quantity`. .EXAMPLE Add-Table 'Items' { TinyInt 'Quantity' -Sparse } Demonstrates how to create a sparse, optional `tinyint` column called `Quantity`. .EXAMPLE Add-Table 'Items' { TinyInt 'Quantity' -NotNull -Default '0' } Demonstrates how to create a required `tinyint` column called `Quantity` with a default value of `0`. .EXAMPLE Add-Table 'Items' { TinyInt 'Quantity' -NotNull -Description 'The number of items currently on hand.' } Demonstrates how to create a required `tinyint` column with a description. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,ParameterSetName='Identity')] [Parameter(Mandatory,ParameterSetName='IdentityWithSeed')] # The column should be an identity. [switch]$Identity, [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=1)] # The starting value for the identity. [int]$Seed, [Parameter(Mandatory,ParameterSetName='IdentityWithSeed',Position=2)] # The increment between auto-generated identity values. [int]$Increment, [Parameter(ParameterSetName='Identity')] [Parameter(ParameterSetName='IdentityWithSeed')] # Stops the identity from being replicated. [switch]$NotForReplication, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::TinyInt($Name, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::TinyInt($Name, [Rivet.Nullable]::NotNull, $Default, $DefaultConstraintName, $Description) } 'Identity' { $i = New-Object 'Rivet.Identity' $NotForReplication [Rivet.Column]::TinyInt($Name, $i, $Description) } 'IdentityWithSeed' { $i = New-Object 'Rivet.Identity' $Seed, $Increment, $NotForReplication [Rivet.Column]::TinyInt($Name, $i, $Description) } } } Set-Alias -Name 'TinyInt' -Value 'New-TinyIntColumn' function New-UniqueIdentifierColumn { <# .SYNOPSIS Creates a column object representing an UniqueIdentifier datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'WithUUID' { UniqueIdentifier 'ColumnName' } ## ALIASES * UniqueIdentifier .EXAMPLE Add-Table Locations { UniqueIdentifier 'LocationID' } Demonstrates how to create a table with an optional `uniqueidentifier` column. .EXAMPLE Add-Table Locations { UniqueIdentifier 'LocationID' -RowGuidCol } Demonstrates how to create a table with an optional `uniqueidentifier`, which is used as the RowGuid identifier for SQL Server replication. .EXAMPLE Add-Table Locations { UniqueIdentifier 'LocationID' -NotNull } Demonstrates how to create a table with an required `uniqueidentifier` column. .EXAMPLE Add-Table Locations { UniqueIdentifier 'LocationID' -Default 'newid()' } Demonstrates how to create a table with an optional `uniqueidentifier` column with a default value. .EXAMPLE Add-Table Locations { UniqueIdentifier 'LocationID' -Description 'The unique identifier for this location.' } Demonstrates how to create a table with an optional `uniqueidentifier` column with a default value. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, # Sets RowGuidCol [switch]$RowGuidCol, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) switch ($PSCmdlet.ParameterSetName) { 'Nullable' { $nullable = 'Null' if( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::UniqueIdentifier($Name, $RowGuidCol, $nullable, $Default, $DefaultConstraintName, $Description) } 'NotNull' { [Rivet.Column]::UniqueIdentifier($Name, $RowGuidCol, [Rivet.Nullable]::NotNull, $Default, $DefaultConstraintName, $Description) } } } Set-Alias -Name 'UniqueIdentifier' -Value 'New-UniqueIdentifierColumn' function New-VarBinaryColumn { <# .SYNOPSIS Creates a column object representing an VarBinary datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table 'Images' { VarBinary 'Bits' 8000 } ## ALIASES * VarBinary .EXAMPLE Add-Table 'Images' { VarBinary 'Bytes' 8000 } Demonstrates how to create an optional `varbinary` column with a maximum length of 8000 bytes. .EXAMPLE Add-Table 'Images' { VarBinary 'Bytes' 8000 -NotNull } Demonstrates how to create a required `varbinary` column with maximum length of 8000 bytes. .EXAMPLE Add-Table 'Images' { VarBinary 'Bytes' -Max } Demonstrates how to create an optional `varbinary` column with the maximum length (2^31 -1 bytes). .EXAMPLE Add-Table 'Images' { VarBinary 'Bytes' -Max -FileStream } Demonstrates now to create an optional `varbinary` column with the maximum length, and stores the data in a filestream data container. #> [CmdletBinding(DefaultParameterSetName='NullSize')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,Position=1,ParameterSetName='NullSize')] [Parameter(Mandatory,Position=1,ParameterSetName='NotNullSize')] # The maximum number of bytes the column will hold. [int]$Size, [Parameter(Mandatory,ParameterSetName='NullMax')] [Parameter(Mandatory,ParameterSetName='NotNullMax')] # Creates a `varbinary(max)` column. [switch]$Max, [Parameter(ParameterSetName='NullMax')] [Parameter(ParameterSetName='NotNullMax')] # Stores the varbinary(max) data in a filestream data container on the file system. Requires VarBinary(max). [switch]$FileStream, [Parameter(Mandatory,ParameterSetName='NotNullSize')] [Parameter(Mandatory,ParameterSetName='NotNullMax')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='NullSize')] [Parameter(ParameterSetName='NullMax')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) $sizeType = $null if( $PSCmdlet.ParameterSetName -like '*Size' ) { $sizeType = New-Object Rivet.CharacterLength $Size } else { $sizeType = New-Object Rivet.CharacterLength @() } $nullable = 'Null' if( $PSCmdlet.ParameterSetName -like 'NotNull*' ) { $nullable = 'NotNull' } elseif( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::VarBinary($Name, $sizeType, $FileStream, $nullable, $Default, $DefaultConstraintName, $Description) } Set-Alias -Name 'VarBinary' -Value 'New-VarBinaryColumn' function New-VarCharColumn { <# .SYNOPSIS Creates a column object representing an VarChar datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table -Name 'WithVarCharColumn' -Column { VarChar 'ColumnName' 50 } ## ALIASES * VarChar .EXAMPLE Add-Table 'Albums' { VarChar 'Name' 100 } Demonstrates how to create an optional `varchar` column with a maximum length of 100 bytes. .EXAMPLE Add-Table 'Albums' { VarChar 'Name' 100 -NotNull } Demonstrates how to create a required `varchar` column with maximum length of 100 bytes. .EXAMPLE Add-Table 'Albums' { VarChar 'Name' -Max } Demonstrates how to create an optional `varchar` column with the maximum length (about 2GB). .EXAMPLE Add-Table 'Albums' { VarChar 'Name' 100 -Collation 'Latin1_General_BIN' } Demonstrates now to create an optional `varchar` column with a custom `Latin1_General_BIN` collation. #> [CmdletBinding(DefaultParameterSetName='NullSize')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Mandatory,Position=1,ParameterSetName='NullSize')] [Parameter(Mandatory,Position=1,ParameterSetName='NotNullSize')] # The maximum length of the column, i.e. the number of characters. [int]$Size, [Parameter(Mandatory,ParameterSetName='NullMax')] [Parameter(Mandatory,ParameterSetName='NotNullMax')] # Create a `varchar(max)` column. [switch]$Max, # Controls the code page that is used to store the data [String]$Collation, [Parameter(Mandatory,ParameterSetName='NotNullSize')] [Parameter(Mandatory,ParameterSetName='NotNullMax')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='NullSize')] [Parameter(ParameterSetName='NullMax')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) $sizeType = $null if( $PSCmdlet.ParameterSetName -like '*Size' ) { $sizeType = New-Object Rivet.CharacterLength $Size } else { $sizeType = New-Object Rivet.CharacterLength @() } $nullable = 'Null' if( $PSCmdlet.ParameterSetName -like 'NotNull*' ) { $nullable = 'NotNull' } elseif( $Sparse ) { $nullable = 'Sparse' } [Rivet.Column]::VarChar($Name, $sizeType, $Collation, $nullable, $Default, $DefaultConstraintName, $Description) } Set-Alias -Name 'VarChar' -Value 'New-VarCharColumn' function New-XmlColumn { <# .SYNOPSIS Creates a column object representing an Xml datatype. .DESCRIPTION Use this function in the `Column` script block for `Add-Table`: Add-Table -Name 'WebConfigs' -Column { Xml 'WebConfig' -XmlSchemaCollection 'webconfigschema' } Remember you have to have already created the XML schema before creating a column that uses it. ## ALIASES * Xml .EXAMPLE Add-Table 'WebConfigs' { Xml 'WebConfig' -XmlSchemaCollection 'webconfigschema' } Demonstrates how to create an optional `xml` column which uses the `webconfigschema` schema collection. .EXAMPLE Add-Table 'WebConfigs' { Xml 'WebConfig' -XmlSchemaCollection 'webconfigschema' -NotNull } Demonstrates how to create a required `xml` column. .EXAMPLE Add-Table 'WebConfigs' { Xml 'WebConfig' -XmlSchemaCollection 'webconfigschema'' -Document } Demonstrates how to create an `xml` column that holds an entire XML document. #> [CmdletBinding(DefaultParameterSetName='Nullable')] param( [Parameter(Mandatory,Position=0)] # The column's name. [String]$Name, [Parameter(Position=1)] # Name of an XML schema collection [String]$XmlSchemaCollection, # Specifies that this is a well-formed XML document instead of an XML fragment. [switch]$Document, [Parameter(Mandatory,ParameterSetName='NotNull')] # Don't allow `NULL` values in this column. [switch]$NotNull, [Parameter(ParameterSetName='Nullable')] # Store nulls as Sparse. [switch]$Sparse, # A SQL Server expression for the column's default value [String]$Default, # The name of the default constraint for the column's default expression. Required if the Default parameter is given. [String]$DefaultConstraintName, # A description of the column. [String]$Description ) $nullable = [Rivet.Nullable]::Null if( $PSCmdlet.ParameterSetName -eq 'NotNull' ) { $nullable = [Rivet.Nullable]::NotNull } else { if( $Sparse ) { $nullable = [Rivet.Nullable]::Sparse } } if( $XmlSchemaCollection ) { [Rivet.Column]::Xml($Name, $Document, $XmlSchemaCollection, $nullable, $Default, $DefaultConstraintName, $Description) } else { [Rivet.Column]::Xml($Name, $nullable, $Default, $DefaultConstraintName, $Description) } } Set-Alias -Name 'Xml' -Value 'New-XmlColumn' function Add-CheckConstraint { <# .SYNOPSIS Add a check constraint to a table. .DESCRIPTION Check constraints add validation for data in columns. .EXAMPLE Add-CheckConstraint 'Migrations' 'CK_Migrations_MigrationID' 'MigrationID > 0' Demonstrates how to add a check constraint to a column that requires the value to be greater than 0. .EXAMPLE Add-CheckConstraint 'Migrations' 'CK_Migrations_MigrationID' 'MigrationID > 0' -NoCheck Demonstrates how to add a check constraint to a column without validating the current contents of the table against this check. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the check constraint's table. $TableName, [Parameter()] [string] # The schema of the table. Default is `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1)] [string] # The name of the check constraint. $Name, [Parameter(Mandatory=$true,Position=2)] [string] # The expression to use for the constraint. $Expression, [Switch] # Don't use the check constraint when inserting, updating, or deleting rows during replication. $NotForReplication, [Switch] # Specifies that the data in the table is not validated against a newly added CHECK constraint. If not specified, WITH CHECK is assumed for new constraints. $NoCheck ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.AddCheckConstraintOperation' $SchemaName, $TableName, $Name, $Expression, $NotForReplication, $NoCheck } function Add-DataType { <# .SYNOPSIS Creates an alias or user-defined type. .DESCRIPTION There are three different user-defined data types. The first is an alias, from a name you choose to a system datatype. The second is an assembly type, which uses a type stored in a .NET assembly. The third is a table data type, which create a type for a table. .LINK Remove-DataType .LINK http://technet.microsoft.com/en-us/library/ms175007.aspx .EXAMPLE Add-DataType 'GUID' 'uniqueidentifier' Demonstrates how to create a new alias data type called `GUID` which aliases the system `uniqueidentifier`. .EXAMPLE Add-DataType 'Names' -AsTable { varchar 'Name' 50 } -TableConstraint 'primary key' Demonstrates how to create a new table-based data type. .EXAMPLE Add-DataType 'RivetDateTime' -AssemblyName 'Rivet' -ClassName 'Rivet.RivetDateTime' Demonstrates how to create a `RivetDateTime` type that references the `Rivet.RivetDateTime` class. The `Rivet` assembly must first be registered using `create assembly`. #> [CmdletBinding(DefaultParameterSetName='From')] param( [Parameter()] [string] # The schema for the type. Default is `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=0)] [string] # The name of the type. $Name, [Parameter(Mandatory=$true,Position=1,ParameterSetName='From')] [string] # The system type to alias. $From, [Parameter(Mandatory=$true,ParameterSetName='Assembly')] [string] # The name of the assembly for the type's implementation. $AssemblyName, [Parameter(Mandatory=$true,ParameterSetName='Assembly')] [string] # The name of the type's class implementation. $ClassName, [Parameter(Mandatory=$true,ParameterSetName='AsTable')] [ScriptBlock] # A `ScriptBlock` which returns columns for the table. $AsTable, [Parameter(ParameterSetName='AsTable')] [string[]] # A list of table constraints for a table-based data type. $TableConstraint ) Set-StrictMode -Version 'Latest' if ($PsCmdlet.ParameterSetName -eq 'From') { $op = New-Object 'Rivet.Operations.AddDataTypeOperation' $SchemaName, $Name, $From } if ($PsCmdlet.ParameterSetName -eq 'Assembly') { $op = New-Object 'Rivet.Operations.AddDataTypeOperation' $SchemaName, $Name, $AssemblyName, $ClassName } if ($PsCmdlet.ParameterSetName -eq 'AsTable') { # Process Column Scriptblock -> Rivet.Column[] [Rivet.Column[]]$columns = & $AsTable $op = New-Object 'Rivet.Operations.AddDataTypeOperation' $SchemaName, $Name, $columns, ([string[]]$TableConstraint) } return $op } function Add-DefaultConstraint { <# .SYNOPSIS Creates a Default constraint to an existing column .DESCRIPTION The DEFAULT constraint is used to insert a default value into a column. The default value will be added to all new records, if no other value is specified. .LINK Add-DefaultConstraint .EXAMPLE Add-DefaultConstraint -TableName Cars -ColumnName Year -Expression '2015' Adds an Default constraint on column 'Year' in the table 'Cars' #> [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] # The name of the target table. [String]$TableName, # The schema name of the target table. Defaults to `dbo`. [String]$SchemaName = 'dbo', [Parameter(Mandatory,Position=1)] # The column on which to add the default constraint [String]$ColumnName, # The name for the default constraint. [String]$Name, [Parameter(Mandatory,Position=2)] #The default expression [String]$Expression, # WithValues [switch]$WithValues ) Set-StrictMode -Version 'Latest' [Rivet.Operations.AddDefaultConstraintOperation]::new($SchemaName, $TableName, $Name, $ColumnName, $Expression, $WithValues) } function Add-Description { <# .SYNOPSIS Adds the `MS_Description` extended property to a table or column. .DESCRIPTION The `sys.sp_addextendedproperty` stored procedure is used to set a table/column's description (i.e. the `MS_Description` extended property), but the syntax is weird. This function hides that weirdness from you. You're welcome. .EXAMPLE Add-Description -Description 'Whoseit's whatsits table.' -TableName WhoseitsWhatsits Adds a description (i.e. the `MS_Description` extended property) on the `WhoseitsWhatsits` table. .EXAMPLE Add-Description -Description 'Is it a snarfblat?' -TableName WhoseitsWhatsits -ColumnName IsSnarfblat Adds a description (i.e. the `MS_Description` extended property) on the `WhoseitsWhatsits` table's `IsSnarfblat` column. .EXAMPLE Add-Description -Description 'Whoseit's whatsits table.' -TableName WhoseitsWhatsits -ForTable PowerShell v2.0 doesn't parse the parameters correctly when setting a table name, so you have to explicitly tell it what to do. Upgrade to PowerShell 3! #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The value for the MS_Description extended property. $Description, [Alias('Schema')] [string] # The schema. Defaults to `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true)] [Alias('Table')] [string] # The name of the table where the extended property is getting set. $TableName, [Parameter(ParameterSetName='ForColumn')] [Alias('Column')] [string] # The name of the column where the extended property is getting set. $ColumnName ) $optionalArgs = @{ } if( $ColumnName ) { $optionalArgs.ColumnName = $ColumnName } Add-ExtendedProperty -Name ([Rivet.Operations.ExtendedPropertyOperation]::DescriptionPropertyName) ` -Value $Description ` -SchemaName $SchemaName ` -TableName $TableName ` @optionalArgs } function Add-ExtendedProperty { <# .SYNOPSIS Adds an extended property for a schema, table, view or column. .DESCRIPTION SQL Server has a special stored procedure for adding extended property metatdata about an object. Unfortunately, it has a really clunky interface. This function is an attempt to wrap `sp_addextendedproperty` with a saner interface. Currently, this function only supports adding properties for schemas, tables, and columns. Submit a patch! .LINK Add-Description .LINK Remove-Description .LINK Remove-ExtendedProperty .LINK Update-Description .LINK Update-ExtendedProperty .EXAMPLE Add-ExtendedProperty -Name 'Deploy' -Value 'TRUE' -SchemaName 'spike' Adds custom `Deploy` metadata for the `spike` schema. .EXAMPLE Add-ExtendedProperty -Name 'Deploy' -Value 'TRUE' -TableName 'Food' Adds custom `Deploy` metadata on the `Food` table in the `dbo` schema. .EXAMPLE Add-ExtendedProperty -Name 'IsEncrypted' -Value 'FALSE' -TableName 'User' -ColumnName 'Password' Adds custom `IsEncrypted` metadata on the `User` table's `Password` column. .EXAMPLE Add-ExtendedProperty -Name 'ContainsPII' -Value 'FALSE' -View 'LoggedInUsers' Demonstrates how to add custom metadata on the `LoggedInUsers` view .EXAMPLE Add-ExtendedProperty -Name 'IsEncrypted' -Value 'FALSE' -View 'LoggedInUsers' -ColumnName 'Password' Demonstrates how to add custom metadata for a view's column #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the extended property to add. $Name, [Parameter(Mandatory=$true,Position=1)] [AllowNull()] # The value of the extended property. $Value, [Parameter(ParameterSetName='SCHEMA')] [Parameter(ParameterSetName='TABLE')] [Parameter(ParameterSetName='TABLE-COLUMN')] [Parameter(ParameterSetName='VIEW')] [Parameter(ParameterSetName='VIEW-COLUMN')] [string] # The schema of the object. $SchemaName = 'dbo', [Parameter(Mandatory=$true,ParameterSetName='TABLE')] [Parameter(Mandatory=$true,ParameterSetName='TABLE-COLUMN')] [Alias('Table')] [string] # The table name. $TableName, [Parameter(Mandatory=$true,ParameterSetName='VIEW')] [Parameter(Mandatory=$true,ParameterSetName='VIEW-COLUMN')] [Alias('View')] [string] # The table name. $ViewName, [Parameter(Mandatory=$true,ParameterSetName='VIEW-COLUMN')] [Parameter(Mandatory=$true,ParameterSetName='TABLE-COLUMN')] [Alias('Column')] [string] # The column name. $ColumnName ) Set-StrictMode -Version 'Latest' if ($PsCmdlet.ParameterSetName -eq "SCHEMA") { $op = New-Object 'Rivet.Operations.AddExtendedPropertyOperation' $SchemaName, $Name, $Value } if ($PsCmdlet.ParameterSetName -eq "TABLE") { $op = New-Object 'Rivet.Operations.AddExtendedPropertyOperation' $SchemaName, $TableName, $Name, $Value, $false } if ($PsCmdlet.ParameterSetName -eq "VIEW") { $op = New-Object 'Rivet.Operations.AddExtendedPropertyOperation' $SchemaName, $ViewName, $Name, $Value, $true } if ($PsCmdlet.ParameterSetName -eq "TABLE-COLUMN") { $op = New-Object 'Rivet.Operations.AddExtendedPropertyOperation' $SchemaName, $TableName, $ColumnName, $Name, $Value, $false } if ($PsCmdlet.ParameterSetName -eq "VIEW-COLUMN") { $op = New-Object 'Rivet.Operations.AddExtendedPropertyOperation' $SchemaName, $ViewName, $ColumnName, $Name, $Value, $true } return $op } function Add-ForeignKey { <# .SYNOPSIS Adds a foreign key to an existing table that doesn't have a foreign key constraint. .DESCRIPTION Adds a foreign key to a table. The table/column that the foreign key references must have a primary key. If the table already has a foreign key, make sure to remove it with `Remove-ForeignKey`. .LINK Add-ForeignKey .EXAMPLE Add-ForeignKey -TableName Cars -ColumnName DealerID -References Dealer -ReferencedColumn DealerID Adds a foreign key to the 'Cars' table on the 'DealerID' column that references the 'DealerID' column on the 'Dealer' table. .EXAMPLE Add-ForeignKey -TableName 'Cars' -ColumnName 'DealerID' -References 'Dealer' -ReferencedColumn 'DealerID' -OnDelete 'CASCADE' -OnUpdate 'CASCADE' -NotForReplication Adds a foreign key to the 'Cars' table on the 'DealerID' column that references the 'DealerID' column on the 'Dealer' table with the options to cascade on delete and update, and also set notforreplication .EXAMPLE Add-ForeignKey -TableName Cars -ColumnName DealerID -References Dealer -ReferencedColumn DealerID -NoCheck Adds a foreign key to the 'Cars' table on the 'DealerID' column that references the 'DealerID' column on the 'Dealer' table without validating the current contents of the table against this key. #> [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] # The name of the table to alter. [String]$TableName, # The name for the foreign key. [String]$Name, # The schema name of the table. Defaults to `dbo`. [String]$SchemaName = 'dbo', [Parameter(Mandatory,Position=1)] # The column(s) that should be part of the foreign key. [String[]]$ColumnName, [Parameter(Mandatory,Position=2)] # The table that the foreign key references [String]$References, # The schema name of the reference table. Defaults to `dbo`. [String]$ReferencesSchema = 'dbo', [Parameter(Mandatory,Position=3)] # The column(s) that the foreign key references [String[]]$ReferencedColumn, # Specifies what action happens to rows in the table that is altered, if those rows have a referential relationship and the referenced row is deleted from the parent table. The default is NO ACTION. [String]$OnDelete, # Specifies what action happens to rows in the table altered when those rows have a referential relationship and the referenced row is updated in the parent table. The default is NO ACTION. [String]$OnUpdate, # Can be specified for FOREIGN KEY constraints and CHECK constraints. If this clause is specified for a constraint, the constraint is not enforced when replication agents perform insert, update, or delete operations. [switch]$NotForReplication, # Specifies that the data in the table is not validated against a newly added FOREIGN KEY constraint. If not specified, WITH CHECK is assumed for new constraints. [switch]$NoCheck ) Set-StrictMode -Version Latest [Rivet.Operations.AddForeignKeyOperation]::new($SchemaName, $TableName, $Name, $ColumnName, $ReferencesSchema, $references, $ReferencedColumn, $OnDelete, $OnUpdate, $NotForReplication, $NoCheck) } function Add-PrimaryKey { <# .SYNOPSIS Adds a primary key to an existing table that doesn't have a primary key. .DESCRIPTION Adds a primary key to a table. If the table already has a primary key, make sure to remove it with `Remove-PrimaryKey`. .LINK Remove-PrimaryKey .EXAMPLE Add-PrimaryKey -TableName Cars -ColumnName Year,Make,Model Adds a primary key to the `Cars` table on the `Year`, `Make`, and `Model` columns. .EXAMPLE Add-PrimaryKey -TableName Cars -ColumnName Year,Make,Model -NonClustered -Option 'IGNORE_DUP_KEY = ON','DROP_EXISTING=ON' Demonstrates how to create a non-clustered primary key, with some index options. #> [CmdletBinding()] param( # The schema name of the table. Defaults to `dbo`. [String]$SchemaName = 'dbo', # The name for the primary key constraint. [String]$Name, [Parameter(Mandatory,Position=0)] # The name of the table. [String]$TableName, [Parameter(Mandatory,Position=1)] # The column(s) that should be part of the primary key. [String[]]$ColumnName, # Create a non-clustered primary key. [switch]$NonClustered, # An array of primary key options. [String[]]$Option ) Set-StrictMode -Version 'Latest' [Rivet.Operations.AddPrimaryKeyOperation]::New($SchemaName, $TableName, $Name, $ColumnName, $NonClustered, $Option) } function Add-Row { <# .SYNOPSIS Inserts a row of data in a table. .DESCRIPTION To specify which columns to insert into the new row, pass a hashtable as a value to the `Column` parameter. This hashtable should have keys that map to column names, and the value of each key will be used as the value for that column in the row. .EXAMPLE Add-Row -SchemaName 'rivet' 'Migrations' @{ ID = 2013093131104 ; Name = 'AMadeUpMigrationDoNotDoThis' ; Who = 'abadbadman' ; ComputerName 'abadbadcomputer' } Demonstrates how to insert a row into the `rivet.Migrations` table. This is for illustrative purposes only. If you do this yourself, a butterfly loses its wings. .EXAMPLE Add-Row 'Cars' @( @{ Make = 'Toyota' ; Model = 'Celica' }, @{ Make = 'Toyota' ; Model = 'Camry' } ) Demonstrates how to insert multiple rows into a table by passing an array of hashtables. .EXAMPLE @( @{ Make = 'Toyota' ; Model = 'Celica' }, @{ Make = 'Toyota' ; Model = 'Camry' } ) | New-Row 'Cars' Demonstrates how to pipe data into `New-Row` to insert a bunch of rows into the database. #> param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the table. $TableName, [Parameter()] [string] # The schema name of the table. Default is `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1,ValueFromPipeline=$true)] [Hashtable[]] # A hashtable of name/value pairs that map to column names/values that will inserted. $Column, [Switch] # Allow inserting identies. $IdentityInsert ) process { Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.AddRowOperation' $SchemaName, $TableName, $Column, $IdentityInsert } } function Add-RowGuidCol { <# .SYNOPSIS Adds the `rowguidcol` property to a column in a table. .DESCRIPTION The `Add-RowGuidCol` operation adds the `rowguidcol` property to a `uniqueidentifier` column in a table. A table can only have one `rowguidcol` column. If a table has an existing `rowguidcol` column, use `Remove-RowGuidCol` to remove it before adding a new one. The `Add-RowGuidCol` operation was added in Rivet 0.7. .LINK https://msdn.microsoft.com/en-us/library/ms190273.aspx .LINK Remove-RowGuidCol .EXAMPLE Add-RowGuidCol -TableName 'MyTable' -ColumnName 'MyUniqueIdentifier' Demonstrates how to add the `rowguidcol` property to a column in a table. In this example, the `dbo.MyTable` table's `MyUniqueIdentifier` column will get the propery. .EXAMPLE Add-RowGuidCol -SchemaName 'cstm' -TableName 'MyTable' -ColumnName 'MyUniqueIdentifier' Demonstrates how to add the `rowguidcol` property to a column in a table whose schema isn't `dbo`, in this case the `cstm.MyTable` table's `MyUniqueIdentifier` column will get the property. #> [CmdletBinding()] param( [string] # The table's schema. Default is `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=0)] [string] # The table's name. $TableName, [Parameter(Mandatory=$true,Position=1)] [string] # The name of the column that should get the `rowguidcol` property. $ColumnName ) Set-StrictMode -Version 'Latest' New-Object -TypeName 'Rivet.Operations.AddRowGuidColOperation' -ArgumentList $SchemaName,$TableName,$ColumnName } function Add-Schema { <# .SYNOPSIS Creates a new schema. .DESCRIPTION The `Add-Schema` operation creates a new schema in a database. It does so in an idempotent way, i.e. it only creates the schema if it doesn't exist. .EXAMPLE Add-Schema -Name 'rivetexample' Creates the `rivetexample` schema. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [Alias('SchemaName')] [string] # The name of the schema. $Name, [Alias('Authorization')] [string] # The owner of the schema. $Owner ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.AddSchemaOperation' $Name, $Owner } function Add-StoredProcedure { <# .SYNOPSIS Creates a new stored procedure. .DESCRIPTION Creates a new stored procedure. .EXAMPLE Add-StoredProcedure -SchemaName 'rivet' 'ReadMigrations' 'AS select * from rivet.Migrations' Creates a stored procedure to read the migrations from Rivet's Migrations table. Note that in real life, you probably should leave my table alone. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the stored procedure. $Name, [Parameter()] [string] # The schema name of the stored procedure. Defaults to `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1)] [string] # The store procedure's definition, which is everything after the `create procedure [schema].[name]` clause. $Definition ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.AddStoredProcedureOperation' $SchemaName, $Name, $Definition } function Add-Synonym { <# .SYNOPSIS Creates a synonym. .DESCRIPTION SQL Server lets you create synonyms so you can reference an object with a different name, or reference an object in another database with a local name. .LINK http://technet.microsoft.com/en-us/library/ms177544.aspx .EXAMPLE Add-Synonym -Name 'Buzz' -TargetObjectName 'Fizz' Creates a synonym called `Buzz` to the object `Fizz`. .EXAMPLE Add-Synonym -SchemaName 'fiz' -Name 'Buzz' -TargetSchemaName 'baz' -TargetObjectName 'Buzz' Demonstrates how to create a synonym in a different schema. Creates a synonym to the `baz.Buzz` object so that it can referenced as `fiz.Buzz`. .EXAMPLE Add-Synonym -Name 'Buzz' -TargetDatabaseName 'Fizzy' -TargetObjectName 'Buzz' Demonstrates how to create a synonym to an object in a different database. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=1)] [string] # The name of the synonym. $Name, [Parameter()] [string] # The name of the schema where the synonym should be created. $SchemaName = 'dbo', [Parameter()] [string] # The database where the target object is located. Defaults to the current database. $TargetDatabaseName, [Parameter()] [string] # The scheme of the target object. Defaults to `dbo`. $TargetSchemaName = 'dbo', [Parameter(Mandatory=$true,Position=2)] [string] # The target object's name the synonym will refer to. $TargetObjectName ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.AddSynonymOperation' $SchemaName, $Name, $TargetSchemaName, $TargetDatabaseName, $TargetObjectName } function Add-Table { <# .SYNOPSIS Creates a new table in the database. .DESCRIPTION The column's for the table should be created and returned in a script block, which is passed as the value of the `Column` parameter. For example, Add-Table 'Suits' { Int 'id' -Identity TinyInt 'pieces -NotNull VarChar 'color' -NotNull } .LINK bigint .LINK binary .LINK bit .LINK char .LINK date .LINK datetime .LINK datetime2 .LINK datetimeoffset .LINK decimal .LINK float .LINK hierarchyid .LINK int .LINK money .LINK nchar .LINK numeric .LINK nvarchar .LINK real .LINK rowversion .LINK smalldatetime .LINK smallint .LINK smallmoney .LINK sqlvariant .LINK time .LINK tinyint .LINK uniqueidentifier .LINK varbinary .LINK varchar .LINK xml .EXAMPLE Add-Table -Name 'Ties' -Column { VarChar 'color' -NotNull } Creates a `Ties` table with a single column for each tie's color. Pretty! #> [CmdletBinding(DefaultParameterSetName='AsNormalTable')] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the table. $Name, [string] # The table's schema. Defaults to 'dbo'. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1,ParameterSetName='AsNormalTable')] [ScriptBlock] # A script block that returns the table's columns. $Column, [Parameter(Mandatory=$true,ParameterSetName='AsFileTable')] [Switch] # Creates a [FileTable](http://msdn.microsoft.com/en-us/library/ff929144.aspx) table. $FileTable, [string] # Specifies the partition scheme or filegroup on which the table is stored, e.g. `ON $FileGroup` $FileGroup, [string] # The filegroup where text, ntext, image, xml, varchar(max), nvarchar(max), and varbinary(max) columns are stored. The table has to have one of those columns. For example, `TEXTIMAGE_ON $TextImageFileGroup`. $TextImageFileGroup, [string] # Specifies the filegroup for FILESTREAM data, e.g. `FILESTREAM_ON $FileStreamFileGroup`. $FileStreamFileGroup, [string[]] # Specifies one or more table options. $Option, [string] # A description of the table. $Description ) Set-StrictMode -Version 'Latest' $columns = & $Column $tableOp = New-Object 'Rivet.Operations.AddTableOperation' $SchemaName, $Name, $columns, $FileTable, $FileGroup, $TextImageFileGroup, $FileStreamFileGroup, $Option $addDescriptionArgs = @{ SchemaName = $SchemaName; TableName = $Name; } if( $Description ) { $tableDescriptionOp = Add-Description -Description $Description @addDescriptionArgs $tableOp.ChildOperations.Add($tableDescriptionOp) } $tableOp | Write-Output $tableOp.ChildOperations | Write-Output foreach( $columnItem in $columns ) { if( $columnItem.Description ) { Add-Description -Description $columnItem.Description -ColumnName $columnItem.Name @addDescriptionArgs | Write-Output } } } function Add-Trigger { <# .SYNOPSIS Creates a new trigger. .DESCRIPTION Creates a new trigger. If updating an existing trigger, use `Remove-Trigger` to remove it first, then `New-Trigger` to re-create it. .LINK Remove-Trigger. .EXAMPLE Add-Trigger 'PrintMessage' 'ON rivet.Migrations for insert as print ''Migration applied!''' Creates a trigger that prints a method when a row gets inserted into the `rivet.Migrations` table. #> param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the trigger. $Name, [Parameter()] [string] # The schema of the trigger. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1)] [string] # The body of the trigger. Everything after and including the `ON` clause. $Definition ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.AddTriggerOperation' $SchemaName, $Name, $Definition } function Add-UniqueKey { <# .SYNOPSIS Creates a UNIQUE constraint on the specified column and table. .DESCRIPTION Creates a UNIQUE constraint on the specified column and table. You can use UNIQUE constraints to make sure that no duplicate values are entered in specific columns that do not participate in a primary key. Although both a UNIQUE constraint and a PRIMARY KEY constraint enforce uniqueness, use a UNIQUE constraint instead of a PRIMARY KEY constraint when you want to enforce the uniqueness of a column, or combination of columns, that is not the primary key. .EXAMPLE Add-UniqueKey -TableName Cars -ColumnName Year Adds an unique constraint on column 'Year' in the table 'Cars' .EXAMPLE Add-UniqueKey -TableName 'Cars' -ColumnName 'Year' -Option @('IGNORE_DUP_KEY = ON','ALLOW_ROW_LOCKS = OFF') Adds an unique constraint on column 'Year' in the table 'Cars' with specified options #> [CmdletBinding()] param( # The schema name of the target table. Defaults to `dbo`. [String]$SchemaName = 'dbo', [Parameter(Mandatory,Position=0)] # The name of the target table. [String]$TableName, # The name for the <object type>. If not given, a sensible name will be created. [String]$Name, [Parameter(Mandatory,Position=1)] # The column(s) on which the index is based [String[]]$ColumnName, # Creates a clustered index, otherwise non-clustered [switch]$Clustered, # FillFactor as Integer [int]$FillFactor, # An array of index options. [String[]]$Option, # The value of the `ON` clause, which controls the filegroup/partition to use for the index. [String]$On ) Set-StrictMode -Version Latest [Rivet.Operations.AddUniqueKeyOperation]::new($SchemaName, $TableName, $Name, $ColumnName, $Clustered, $FillFactor, $Option, $On) } function Add-UserDefinedFunction { <# .SYNOPSIS Creates a new user-defined function. .DESCRIPTION Creates a new user-defined function. .EXAMPLE Add-UserDefinedFunction -SchemaName 'rivet' 'One' 'returns tinyint begin return 1 end' Creates a user-defined function that returns the number 1. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the stored procedure. $Name, [Parameter()] [string] # The schema name of the stored procedure. Defaults to `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1)] [string] # The store procedure's definition. Everything after the `create function [schema].[name]` clause. $Definition ) Set-StrictMode -Version 'Latest' New-Object Rivet.Operations.AddUserDefinedFunctionOperation $SchemaName,$Name,$Definition } function Add-View { <# .SYNOPSIS Creates a new view. .DESCRIPTION Creates a new view. .EXAMPLE Add-View -SchemaName 'rivet' 'ReadMigrations' 'AS select * from rivet.Migrations' Creates a view to read all the migrations from Rivet's Migrations table. Don't do this in real life. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the view. $Name, [Parameter()] [string] # The schema name of the view. Defaults to `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1)] [string] # The definition of the view. Everything after the `create view [schema].[name]` clause. $Definition ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.AddViewOperation' $SchemaName,$Name,$Definition } function Disable-Constraint { <# .SYNOPSIS Disable a check of foreign key constraint on a table. .DESCRIPTION The `Disable-Constraint` operation disables a check or foreign key constraint on a table. Only check and foreign key constraints can be enabled/disabled. .LINK Enable-Constraint .EXAMPLE Disable-CheckConstraint 'Migrations' 'CK_Migrations_MigrationID' Demonstrates how to disable a constraint on a table. In this case, the `CK_Migrations_MigrationID` constraint on the `Migrations` table is disabled. Is it a check constraint? Foreign key constraint? It doesn't matter! #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the constraint's table. $TableName, [Parameter()] [string] # The schema of the table. Default is `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1)] [string] # The name of the constraint. $Name ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.DisableConstraintOperation' $SchemaName, $TableName, $Name } Set-Alias -Name 'Disable-CheckConstraint' -Value 'Disable-Constraint' function Disable-ForeignKey { <# .SYNOPSIS OBSOLETE. Use `Disable-Constraint` instead. .DESCRIPTION OBSOLETE. Use `Disable-Constraint` instead. .EXAMPLE Disable-Constraint 'SourceTable' 'FK_SourceID_ReferenceTable' Demonstrates that `Disable-ForeignKey` is obsolete by showing that you should use `Disable-Constraint` instead. #> [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] # The name of the table to alter. [String]$TableName, # The schema name of the table. Defaults to `dbo`. [String]$SchemaName = 'dbo', [Parameter(Mandatory,Position=1)] # The column(s) that should be part of the foreign key. [String[]]$ColumnName, [Parameter(Mandatory,Position=2)] # The table that the foreign key references [String]$References, [Parameter()] # The schema name of the reference table. Defaults to `dbo`. [String]$ReferencesSchema = 'dbo', # The name for the <object type>. If not given, a sensible name will be created. [String]$Name ) Set-StrictMode -Version 'Latest' Write-Warning ('The "Disable-ForeignKey" operation is obsolete and will removed in a future version of Rivet. Please use "Disable-Constraint" instead.') if( -not $PSBoundParameters.ContainsKey('Name') ) { $Name = New-ConstraintName -ForeignKey ` -SchemaName $SchemaName ` -TableName $TableName ` -ReferencesSchemaName $ReferencesSchema ` -ReferencesTableName $References } Disable-Constraint -SchemaName $SchemaName -TableName $TableName -Name $Name } function Enable-Constraint { <# .SYNOPSIS Enable a check or foreign key constraint. .DESCRIPTION The `Enable-Constraint` operation enables a check or foreign key constraint on a table. Only check and foreign key constraints can be enabled/disabled. .LINK Disable-Constraint .EXAMPLE Enable-Constraint 'Migrations' 'FK_Migrations_MigrationID' Demonstrates how to disable a constraint on a table. In this case, the `FK_Migrations_MigrationID` constraint on the `Migrations` table is disabled. Is it a check constraint? Foreign key constraint? It doesn't matter! #> [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] # The name of the constraint's table. [String]$TableName, [Parameter()] # The schema of the table. Default is `dbo`. [String]$SchemaName = 'dbo', [Parameter(Mandatory,Position=1)] # The name of the constraint. [String]$Name ) Set-StrictMode -Version 'Latest' [Rivet.Operations.EnableConstraintOperation]::New($SchemaName, $TableName, $Name, $false) } Set-Alias -Name 'Enable-CheckConstraint' -Value 'Enable-Constraint' function Enable-ForeignKey { <# .SYNOPSIS OBSOLETE. Use `Enable-Constraint` instead. .DESCRIPTION OBSOLETE. Use `Enable-Constraint` instead. .EXAMPLE Enable-Constraint 'TAbleName', 'FK_ForeignKeyName' Demonstrates that `Enable-ForeignKey` is obsolete and you should use `Enable-Constraint` instead. #> [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] # The name of the table to alter. [String]$TableName, # The schema name of the table. Defaults to `dbo`. [String]$SchemaName = 'dbo', [Parameter(Mandatory,Position=1)] # The column(s) that should be part of the foreign key. [String[]]$ColumnName, [Parameter(Mandatory,Position=2)] # The table that the foreign key references [String]$References, # The schema name of the reference table. Defaults to `dbo`. [String]$ReferencesSchema = 'dbo', # The name for the <object type>. If not given, a sensible name will be created. [String]$Name ) Set-StrictMode -Version 'Latest' Write-Warning ('The "Enable-ForeignKey" operation is obsolete and will removed in a future version of Rivet. Please use "Enable-Constraint" instead.') if( -not $PSBoundParameters.ContainsKey('Name') ) { $Name = New-ConstraintName -ForeignKey ` -SchemaName $SchemaName ` -TableName $TableName ` -ReferencesSchemaName $ReferencesSchema ` -ReferencesTableName $References } Enable-Constraint -SchemaName $SchemaName -TableName $TableName -Name $Name } function Invoke-Ddl { <# .SYNOPSIS Executes a DDL statement against the database. .DESCRIPTION The `Invoke-Ddl` function is used to update the structure of a database when none of Rivet's other operations will work. .EXAMPLE Invoke-Ddl -Query 'create table rivet.Migrations ( id int not null )' Executes the create table syntax above against the database. #> [CmdletBinding(DefaultParameterSetName='AsReader')] param( [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)] [string] $Query ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RawDdlOperation' $Query } function Invoke-SqlScript { <# .SYNOPSIS Runs a SQL script file as part of a migration. .DESCRIPTION The SQL script is split on GO statements, which must be by themselves on a line, e.g. select * from sys.tables GO select * from sys.views GO #> [CmdletBinding(DefaultParameterSetName='AsReader')] param( [Parameter(Mandatory=$true)] [string] # The path to the SQL script to execute. $Path, [Parameter(Mandatory=$true,ParameterSetName='AsScalar')] [Switch] $AsScalar, [Parameter(Mandatory=$true,ParameterSetName='AsNonQuery')] [Switch] $NonQuery, [UInt32] # The time in seconds to wait for the command to execute. The default is 30 seconds. $CommandTimeout = 30 ) Set-StrictMode -Version 'Latest' $invokeMigrationParams = @{ CommandTimeout = $CommandTimeout; } if( $pscmdlet.ParameterSetName -eq 'AsScalar' ) { $invokeMigrationParams.AsScalar = $true } elseif( $pscmdlet.ParameterSetName -eq 'AsNonQuery' ) { $invokeMigrationParams.NonQuery = $true } if( -not ([IO.Path]::IsPathRooted( $Path )) ) { $Path = Join-Path $DBMigrationsRoot $Path } if( -not (Test-Path -Path $Path -PathType Leaf) ) { throw ('SQL script ''{0}'' not found.' -f $Path) return } $Path = Resolve-Path -Path $Path | Select-Object -ExpandProperty 'ProviderPath' $sql = Get-Content -Path $Path -Raw New-Object 'Rivet.Operations.ScriptFileOperation' $Path,$sql } function Remove-CheckConstraint { <# .SYNOPSIS Removes a check constraint from a table. .DESCRIPTION The `Remove-CheckConstraint` operation removes a check constraint from a table. Check constraints add validation for data in columns. .EXAMPLE Remove-CheckConstraint 'Migrations' 'CK_Migrations_MigrationID' Demonstrates how to remove a check constraint from a table. In this case, the `CK_Migrations_MigrationID` constraint will be removed from the `Migrations` table. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the check constraint's table. $TableName, [Parameter()] [string] # The schema of the table. Default is `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1)] [string] # The name of the check constraint to remove. $Name ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RemoveCheckConstraintOperation' $SchemaName, $TableName, $Name } function Remove-DataType { <# .SYNOPSIS Drops a user-defined datatype. .DESCRIPTION Handles all three datatypes: alias, CLR, and table. If the datatype is in use, you'll get an error. Make sure to remove/alter any objects that reference the type first. .LINK Add-DataType .LINK http://technet.microsoft.com/en-us/library/ms174407.aspx .EXAMPLE Remove-DataType 'GUID' Demonstrates how to remove the `GUID` user-defined data type. .EXAMPLE Remove-DataType -SchemaName 'rivet' 'GUID' Demonstrates how to remove a datatype in a schema other than `dbo`. #> [CmdletBinding()] param( [Parameter()] [string] # The name of the type's schema. Default is `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=0)] [string] # The name of the datatype to drop. $Name ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RemoveDataTypeOperation' $SchemaName, $Name } function Remove-DefaultConstraint { <# .SYNOPSIS Removes a default constraint from a table. .DESCRIPTION The `Remove-DefaultConstraint` operation removes a default constraint from a table. .EXAMPLE Remove-DefaultConstraint 'Cars' -ColumnName 'Year' -Name 'Cars_Year_DefaultConstraint' Demonstrates how to remove a default constraint. In this case, the `Cars_Year_DefaultConstraint` constraint will be removed from the `Cars` table. #> [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] # The name of the target table. [String]$TableName, # The schema name of the target table. Defaults to `dbo`. [String]$SchemaName = 'dbo', [Parameter(Position=1)] # The column name. [String]$ColumnName, # The name of the default constraint to remove. [String]$Name ) Set-StrictMode -Version 'Latest' if( -not $Name ) { if( -not $ColumnName ) { Write-Error -Message ('The Name parameter is mandatory. Please pass the name of the default constraint to the Name parameter.') -ErrorAction Stop return } } if( -not $ColumnName ) { $nameMsg = '' if( $Name ) { $nameMsg = "'s $($Name) constraint" } $msg = ('The ColumnName parameter will be required in a future version of Rivet. Add a "ColumnName" ' + "parameter to the Remove-DefaulConstraint operation for the [$($SchemaName)].[$($TableName)] " + "table$($nameMsg).") Write-Warning -Message $msg } [Rivet.Operations.RemoveDefaultConstraintOperation]::New($SchemaName, $TableName, $ColumnName, $Name) } function Remove-Description { <# .SYNOPSIS Removes the `MS_Description` extended property for a table or column. .DESCRIPTION The `sys.sp_dropextendedproperty` stored procedure is used to remove a table/column's description (i.e. the `MS_Description` extended property), but the syntax is weird. This function hides that weirdness from you. You're welcome. .EXAMPLE Remove-Description -TableName WhoseitsWhatsits Removes the description (i.e. the `MS_Description` extended property) for the `WhoseitsWhatsits` table. .EXAMPLE Remove-Description -TableName WhoseitsWhatsits -ColumnName IsSnarfblat Removes the description (i.e. the `MS_Description` extended property) for the `WhoseitsWhatsits` table's `IsSnarfblat` column. #> [CmdletBinding()] param( [Alias('Schema')] [string] # The schema. Defaults to `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true)] [Alias('Table')] [string] # The name of the table where the extended property is getting set. $TableName, [Parameter(ParameterSetName='ForColumn')] [Alias('Column')] [string] # The name of the column where the extended property is getting set. $ColumnName ) Set-StrictMode -Version 'Latest' $optionalArgs = @{ } if( $ColumnName ) { $optionalArgs.ColumnName = $ColumnName } Remove-ExtendedProperty -Name 'MS_Description' ` -SchemaName $SchemaName ` -TableName $TableName ` @optionalArgs } function Remove-ExtendedProperty { <# .SYNOPSIS Drops an extended property for a schema, table, or column. .DESCRIPTION SQL Server has a special stored procedure for removing extended property metatdata about an object. Unfortunately, it has a really clunky interface. This function is an attempt to wrap `sp_dropextendedproperty` with a saner interface. Currently, this function only supports dropping properties for schemas, tables, and columns. Submit a patch! .LINK Add-Description .LINK Add-ExtendedProperty .LINK Remove-Description .LINK Update-Description .LINK Update-ExtendedProperty .EXAMPLE Remove-ExtendedProperty -Name 'Deploy' -SchemaName 'spike' Drops the custom `Deploy` metadata for the `spike` schema. .EXAMPLE Remove-ExtendedProperty -Name 'Deploy' -TableName 'Food' Drops the custom `Deploy` metadata on the `Food` table in the `dbo` schema. .EXAMPLE Remove-ExtendedProperty -Name 'IsEncrypted' -TableName 'User' -ColumnName 'Password' Drops the custom `IsEncrypted` metadata on the `User` table's `Password` column. .EXAMPLE Remove-ExtendedProperty -Name 'ContainsPII' -View 'LoggedInUsers' Demonstrates how to remove custom metadata on the `LoggedInUsers` view .EXAMPLE Remove-ExtendedProperty -Name 'IsEncrypted' -View 'LoggedInUsers' -ColumnName 'Password' Demonstrates how to remove custom metadata for a view's column #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the extended property to add. $Name, [Parameter(ParameterSetName='SCHEMA')] [Parameter(ParameterSetName='TABLE')] [Parameter(ParameterSetName='TABLE-COLUMN')] [Parameter(ParameterSetName='VIEW')] [Parameter(ParameterSetName='VIEW-COLUMN')] [string] # The schema of the object. $SchemaName = 'dbo', [Parameter(Mandatory=$true,ParameterSetName='TABLE')] [Parameter(Mandatory=$true,ParameterSetName='TABLE-COLUMN')] [Alias('Table')] [string] # The table name. $TableName, [Parameter(Mandatory=$true,ParameterSetName='VIEW')] [Parameter(Mandatory=$true,ParameterSetName='VIEW-COLUMN')] [Alias('View')] [string] # The table name. $ViewName, [Parameter(Mandatory=$true,ParameterSetName='VIEW-COLUMN')] [Parameter(Mandatory=$true,ParameterSetName='TABLE-COLUMN')] [Alias('Column')] [string] # The column name. $ColumnName ) Set-StrictMode -Version 'Latest' if ($PsCmdlet.ParameterSetName -eq "SCHEMA") { $op = New-Object 'Rivet.Operations.RemoveExtendedPropertyOperation' $SchemaName, $Name } if ($PsCmdlet.ParameterSetName -eq "TABLE") { $op = New-Object 'Rivet.Operations.RemoveExtendedPropertyOperation' $SchemaName, $TableName, $Name, $false } if ($PsCmdlet.ParameterSetName -eq "VIEW") { $op = New-Object 'Rivet.Operations.RemoveExtendedPropertyOperation' $SchemaName, $ViewName, $Name, $true } if ($PsCmdlet.ParameterSetName -eq "TABLE-COLUMN") { $op = New-Object 'Rivet.Operations.RemoveExtendedPropertyOperation' $SchemaName, $TableName, $ColumnName, $Name, $false } if ($PsCmdlet.ParameterSetName -eq "VIEW-COLUMN") { $op = New-Object 'Rivet.Operations.RemoveExtendedPropertyOperation' $SchemaName, $ViewName, $ColumnName, $Name, $true } return $op } function Remove-ForeignKey { <# .SYNOPSIS Removes a foreign key from an existing table that has a foreign key. .DESCRIPTION Removes a foreign key to a table. .EXAMPLE Remove-ForeignKey 'Cars' -Name 'FK_Cars_Year' Demonstrates how to remove a foreign key that has a name different than Rivet's derived name. #> [CmdletBinding(DefaultParameterSetName='ByDefaultName')] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the table. $TableName, [Parameter()] [string] # The schema name of the table. Defaults to `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1,ParameterSetName='ByDefaultName')] [string] # OBSOLETE. Use the `Name` parameter to specify the foreign key to remove. $References, [Parameter(ParameterSetName='ByDefaultName')] [string] # OBSOLETE. Use the `Name` parameter to specify the foreign key to remove. $ReferencesSchema = 'dbo', [Parameter(Mandatory=$true,ParameterSetName='ByCustomName')] [string] # The name of the foreign key to remove. $Name ) Set-StrictMode -Version 'Latest' [Rivet.Operations.RemoveForeignKeyOperation]::New($SchemaName, $TableName, $Name) } function Remove-Index { <# .SYNOPSIS Removes an index from a table. .DESCRIPTION The `Remove-Index` operation removes an index from a table. .EXAMPLE Remove-Index 'Cars' -Name 'YearIX' Demonstrates how to drop an index #> [CmdletBinding(DefaultParameterSetName='ByDefaultName')] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the target table. $TableName, [Parameter()] [string] # The schema name of the target table. Defaults to `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1,ParameterSetName='ByDefaultName')] [string[]] # OBSOLETE. Use the `Name` parameter to remove an index. $ColumnName, [Parameter(ParameterSetName='ByDefaultName')] [Switch] # OBSOLETE. Use the `Name` parameter to remove an index. $Unique, [Parameter(Mandatory=$true,ParameterSetName='ByExplicitName')] [string] # The name of the index to remove. $Name ) Set-StrictMode -Version 'Latest' # TODO: once generating constraint names is out, remove $columnName and $unique parameters. [Rivet.Operations.RemoveIndexOperation]::New($SchemaName, $TableName, $Name, $ColumnName, $Unique) } function Remove-PrimaryKey { <# .SYNOPSIS Removes a primary key from a table. .DESCRIPTION The `Remove-PrimaryKey` operation removes a primary key from a table. .EXAMPLE Remove-PrimaryKey 'Cars' -Name 'Car_PK' Demonstrates how to remove a primary key whose name is different than the derived name Rivet creates for primary keys. #> [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] # The name of the table. [String]$TableName, # The schema name of the table. Defaults to `dbo`. [String]$SchemaName = 'dbo', [Parameter(Position=1)] # The name of the primary key to remove. [String]$Name ) Set-StrictMode -Version 'Latest' [Rivet.Operations.RemovePrimaryKeyOperation]::New($SchemaName, $TableName, $Name) } function Remove-Row { <# .SYNOPSIS Removes a row from a table. .DESCRIPTION To specify which columns to insert into the new row, pass a hashtable as a value to the `Column` parameter. This hashtable should have keys that map to column names, and the value of each key will be used as the value for that column in the row. .EXAMPLE Remove-Row -SchemaName 'rivet' 'Migrations' 'MigrationID=20130913132411' Demonstrates how to delete a specific set of rows from a table. .EXAMPLE Remove-Row 'Cars' -All Demonstrates how to remove all rows in a table. #> param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the table. $TableName, [Parameter()] [string] # The schema name of the table. Default is `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1,ParameterSetName='DropSpecificRows')] [string] # The condition to use for choosing which rows to remove. This parameter is required, unless you *really* want to $Where, [Parameter(Mandatory=$true,ParameterSetName='AllRows')] [Switch] # Drop all the rows in the table. $All, [Parameter(ParameterSetName='AllRows')] [Switch] # Truncate the table instead to delete all the rows. This is faster than using a `delete` statement. $Truncate ) Set-StrictMode -Version 'Latest' if ($PSCmdlet.ParameterSetName -eq 'DropSpecificRows') { New-Object 'Rivet.Operations.RemoveRowOperation' $SchemaName, $TableName, $Where } elseif ($PSCmdlet.ParameterSetName -eq 'AllRows') { if ($Truncate) { New-Object 'Rivet.Operations.RemoveRowOperation' $SchemaName, $TableName, $true } else { New-Object 'Rivet.Operations.RemoveRowOperation' $SchemaName, $TableName, $false } } } function Remove-RowGuidCol { <# .SYNOPSIS Remove the `rowguidcol` property from a column in a table. .DESCRIPTION The `Remove-RowGuidCol` operation removes the `rowguidcol` property from a `uniqueidentifier` column in a table. The `Remove-RowGuidCol` operation was added in Rivet 0.7. .LINK https://msdn.microsoft.com/en-us/library/ms190273.aspx .LINK Add-RowGuidCol .EXAMPLE Remove-RowGuidCol -TableName 'MyTable' -ColumnName 'MyUniqueIdentifier' Demonstrates how to remove the `rowguidcol` property from a column in a table. In this example, the `dbo.MyTable` table's `MyUniqueIdentifier` column will lose the propery. .EXAMPLE Remove-RowGuidCol -SchemaName 'cstm' -TableName 'MyTable' -ColumnName 'MyUniqueIdentifier' Demonstrates how to remove the `rowguidcol` property from a column in a table whose schema isn't `dbo`, in this case the `cstm.MyTable` table's `MyUniqueIdentifier` column will lose the property. #> [CmdletBinding()] param( [string] # The table's schema. Default is `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=0)] [string] # The table's name. $TableName, [Parameter(Mandatory=$true,Position=1)] [string] # The name of the column that should get the `rowguidcol` property. $ColumnName ) Set-StrictMode -Version 'Latest' New-Object -TypeName 'Rivet.Operations.RemoveRowGuidColOperation' -ArgumentList $SchemaName,$TableName,$ColumnName } function Remove-Schema { <# .SYNOPSIS Removes a schema. .EXAMPLE Remove-Schema -Name 'rivetexample' Drops/removes the `rivetexample` schema. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [Alias('SchemaName')] [string] # The name of the schema. $Name ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RemoveSchemaOperation' $Name } function Remove-StoredProcedure { <# .SYNOPSIS Removes a stored procedure. .DESCRIPTION Removes a stored procedure. Will throw an exception and rollback the migration if the stored procedure doesn't exist. By default, the stored procedure is assumed to be in the `dbo` schema. Use the `Schema` parameter to specify a different schema. You can conditionally delete a stored procedure only if it exists using the `IfExists` switch. .EXAMPLE Remove-StoredProcedure -Name MySproc Removes the `dbo.MySproc` stored procedure. .EXAMPLE Remove-StoredProcedure -Name MySproc -SchemaName rivet Removes the `rivet.MySproc` stored procedure. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string] # The name of the stored procedure to remove/delete. $Name, [Parameter()] [string] # The schema of the stored procedure. Default is `dbo`. $SchemaName = 'dbo' ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RemoveStoredProcedureOperation' $SchemaName, $Name } function Remove-Synonym { <# .SYNOPSIS Drops a synonym. .DESCRIPTION Drops an existing synonym. If the synonym doesn't exist, you'll get an error. .LINK http://technet.microsoft.com/en-us/library/ms174996.aspx .EXAMPLE Remove-Synonym -Name 'Buzz' Removes the `Buzz` synonym. .EXAMPLE Remove-Synonym -SchemaName 'fiz' -Name 'Buzz' Demonstrates how to remove a synonym in a schema other than `dbo`. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=1)] [string] # The name of the synonym to drop. $Name, [Parameter()] [string] # The name of the synonym's schema. Default to `dbo`. $SchemaName = 'dbo' ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RemoveSynonymOperation' $SchemaName, $Name } function Remove-Table { <# .SYNOPSIS Removes a table from a database. .DESCRIPTION You can't get any of the data back, so be careful. .EXAMPLE Remove-Table -Name 'Coffee' Removes the `Coffee` table from the database. #> param( # The name of the table where the column should be removed. [Parameter(Mandatory=$true)] [string] $Name, [string] # The schema of the table where the column should be added. Default is `dbo`. $SchemaName = 'dbo' ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RemoveTableOperation' $SchemaName, $Name } function Remove-Trigger { <# .SYNOPSIS Deletes a new trigger. .DESCRIPTION Deletes an existing trigger. .LINK New-Trigger. .EXAMPLE Remove-Trigger 'PrintMessage' Removes the `PrintMessage` trigger. #> param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the trigger. $Name, [Parameter()] [string] # The schema of the trigger. $SchemaName = "dbo" ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RemoveTriggerOperation' $SchemaName, $Name } function Remove-UniqueKey { <# .SYNOPSIS Removes the Unique Constraint from the database .DESCRIPTION Removes the Unique Constraint from the database. .EXAMPLE Remove-UniqueKey 'Cars' -Name 'YearUK' Demonstrates how to remove a unique key whose name is different than the name Rivet derives for unique keys. #> [CmdletBinding(DefaultParameterSetName='ByDefaultName')] param( [Parameter(Mandatory,Position=0)] # The name of the target table. [String]$TableName, [Parameter()] [String] # The schema name of the target table. Defaults to `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory,Position=1,ParameterSetName='ByDefaultName')] # OBSOLETE. Use the `Name` parameter to specify the name of the unique key to remove. [String[]]$ColumnName, [Parameter(Mandatory,Position=1,ParameterSetName='ByExplicitName')] # The name of the unique key to remove. [String]$Name ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RemoveUniqueKeyOperation' $SchemaName, $TableName, $Name, $ColumnName } function Remove-UserDefinedFunction { <# .SYNOPSIS Removes a user-defined function. .DESCRIPTION Removes a user-defined function. Will throw an exception and rollback the migration if the user-defined function doesn't exist. By default, the user-defined function is assumed to be in the `dbo` schema. Use the `Schema` parameter to specify a different schema. You can conditionally delete a user-defined function only if it exists using the `IfExists` switch. .EXAMPLE Remove-UserDefinedFunction -Name MyFunc Removes the `dbo.MyFunc` user-defined function. .EXAMPLE Remove-UserDefinedFunction -Name MyFunc -SchemaName rivet Removes the `rivet.MyFunc` user-defined function. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string] # The name of the user-defined function to remove/delete. $Name, [Parameter()] [string] # The schema of the user-defined function. Default is `dbo`. $SchemaName = 'dbo' ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RemoveUserDefinedFunctionOperation' $SchemaName, $Name } function Remove-View { <# .SYNOPSIS Removes a view. .DESCRIPTION Removes a view. Will throw an exception and rollback the migration if the view doesn't exist. By default, the view is assumed to be in the `dbo` schema. Use the `Schema` parameter to specify a different schema. You can conditionally delete a view only if it exists using the `IfExists` switch. .EXAMPLE Remove-View -Name MyView Removes the `dbo.MyView` view. .EXAMPLE Remove-View -Name MyView -SchemaName rivet Removes the `rivet.MyView` view. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string] # The name of the view to remove/delete. $Name, [Parameter()] [string] # The schema of the view. Default is `dbo`. $SchemaName = 'dbo' ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RemoveViewOperation' $SchemaName, $Name } function Rename-Column { <# .SYNOPSIS Renames a column. .DESCRIPTION SQL Server ships with a stored procedure which is used to rename certain objects. This operation wraps that stored procedure. Use `Rename-DataType` to rename a data type. Use `Rename-Index` to rename an index. Use `Rename-Object` to rename an object. .LINK http://technet.microsoft.com/en-us/library/ms188351.aspx .LINK Rename-DataType .LINK Rename-Index .LINK Rename-Object .EXAMPLE Rename-Column -TableName 'FooBar' -Name 'Fizz' -NewName 'Buzz' Changes the name of the `Fizz` column in the `FooBar` table to `Buzz`. .EXAMPLE Rename-Column -SchemaName 'fizz' -TableName 'FooBar' -Name 'Buzz' -NewName 'Baz' Demonstrates how to rename a column in a table that is in a schema other than `dbo`. .EXAMPLE Rename-Column 'FooBar' 'Fizz' 'Buzz' Demonstrates how to use the short form to rename `Fizz` column in the `FooBar` table to `Buzz`: table name is first, then existing column name, then new column name. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the table of the column to rename. $TableName, [Parameter(Mandatory=$true,Position=1)] [string] # The current name of the column. $Name, [Parameter(Mandatory=$true,Position=2)] [string] # The new name of the column. $NewName, [Parameter()] [string] # The schema of the table. Default is `dbo`. $SchemaName = 'dbo' ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RenameColumnOperation' $SchemaName, $TableName, $Name, $NewName } function Rename-DataType { <# .SYNOPSIS Renames data types. .DESCRIPTION This function wraps the `sp_rename` stored procedure, and can be used to rename `USERDATATYPE` types. Use `Rename-Index` to rename an index. Use `Rename-Column` to rename a column. Use `Rename-Object` to rename an object. .LINK http://technet.microsoft.com/en-us/library/ms188351.aspx .LINK Rename-Column .LINK Rename-Index .LINK Rename-Object .EXAMPLE Rename-DataType -Name 'FooBar' -NewName 'BarFoo' Changes the name of the `FooBar` type to `BarFoo`. .EXAMPLE Rename-DataType -SchemaName 'fizz' -Name 'Buzz' -NewName 'Baz' Demonstrates how to rename a data type that is in a schema other than `dbo`. #> [CmdletBinding()] param( [Parameter()] # The schema of the table. Default is `dbo`. [String]$SchemaName = "dbo", [Parameter(Mandatory,Position=0)] # The current name of the table. [String]$Name, [Parameter(Mandatory,Position=1)] # The new name of the table. [String]$NewName ) Set-StrictMode -Version 'Latest' [Rivet.Operations.RenameDataTypeOperation]::New($SchemaName, $Name, $NewName) } function Rename-Index { <# .SYNOPSIS Renames an index. .DESCRIPTION SQL Server ships with a stored procedure which is used to rename certain objects. This operation wraps that stored procedure. Use `Rename-Column` to rename a column. Use `Rename-DataType` to rename a data type. Use `Rename-Object` to rename an object. .LINK http://technet.microsoft.com/en-us/library/ms188351.aspx .LINK Rename-Column .LINK Rename-DataType .LINK Rename-Object .EXAMPLE Rename-Index -TableName 'FooBar' -Name 'IX_Fizz' -NewName 'Buzz' Changes the name of the `Fizz` index on the `FooBar` table to `Buzz`. .EXAMPLE Rename-Index -SchemaName 'fizz' -TableName 'FooBar' -Name 'IX_Buzz' -NewName 'Fizz' Demonstrates how to rename an index on a table that is in a schema other than `dbo`. .EXAMPLE Rename-Index 'FooBar' 'IX_Fizz' 'Buzz' Demonstrates how to use the short form to rename the `Fizz` index on the `FooBar` table to `Buzz`: table name is first, then existing index name, then new index name. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the table of the index to rename. $TableName, [Parameter(Mandatory=$true,Position=1)] [string] # The current name of the index. $Name, [Parameter(Mandatory=$true,Position=2)] [string] # The new name of the index. $NewName, [Parameter()] [string] # The schema of the table. Default is `dbo`. $SchemaName = 'dbo' ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.RenameIndexOperation' $SchemaName, $TableName, $Name, $NewName } function Rename-Object { <# .SYNOPSIS Renames objects (e.g. tables, constraints, keys). .DESCRIPTION This function wraps the `sp_rename` stored procedure, and can be used to rename objects tracked in `sys.objects`: * Tables * Functions * Synonyms * Constraints/keys * Views * Stored procedures * Triggers Use `Rename-Index` to rename an index. Use `Rename-Column` to rename a column. Use `Rename-DataType` to rename a data type. .LINK http://technet.microsoft.com/en-us/library/ms188351.aspx .LINK Rename-Column .LINK Rename-DataType .LINK Rename-Index .EXAMPLE Rename-Object -Name 'FooBar' -NewName 'BarFoo' Changes the name of the `FooBar` table to `BarFoo`. .EXAMPLE Rename-Object -SchemaName 'fizz' -Name 'Buzz' -NewName 'Baz' Demonstrates how to rename a table that is in a schema other than `dbo`. .EXAMPLE Rename-Object 'FK_Foo_Bar' 'FK_Bar_Foo' Demonstrates how to use `Rename-Object` without explicit parameters, and how to rename a foreign key. #> [CmdletBinding()] param( [Parameter()] [string] # The schema of the table. Default is `dbo`. $SchemaName = "dbo", [Parameter(Mandatory=$true,Position=0)] [string] # The current name of the table. $Name, [Parameter(Mandatory=$true,Position=1)] [string] # The new name of the table. $NewName ) Set-StrictMode -Version 'Latest' [Rivet.Operations.RenameObjectOperation]::New($SchemaName, $Name, $NewName) } function Stop-Migration { <# .SYNOPSIS Stops a migration from getting poppped. .DESCRIPTION The `Stop-Migration` operation stops a migration from getting popped. When put in your migration's `Pop-Migration` function, the migration will fail when someone attempts to pop it. Use this operation to mark a migration as irreversible. `Stop-Migration` was added in Rivet 0.6. .EXAMPLE Stop-Migration Demonstrates how to use use `Stop-Migration`. .EXAMPLE Stop-Migration -Message 'The datatabase's flibbers have been upgraed to flobbers. This operation can't be undone. Sorry.' Demonstrates how to display a message explaining why the migration isn't reversible. #> [CmdletBinding()] param( [string] # A message to show that explains why the migrations isn't reversible. Default message is `This migration is irreversible and can't be popped.`. $Message = 'This migration is irreversible and can''t be popped.' ) Set-StrictMode -Version 'Latest' New-Object -TypeName 'Rivet.Operations.IrreversibleOperation' -ArgumentList $Message } function Update-CodeObjectMetadata { <# .SYNOPSIS Updates the metadata for a stored procedure, user-defined function, view, trigger, etc. .DESCRIPTION SQL Server has a stored procedure, `sys.sp_refreshsqlmodule`, which will refresh/update a the objects used by a code object (stored procedure, user-defined function, view, etc.) if that object has changed since the code object was created. .LINK http://technet.microsoft.com/en-us/library/bb326754.aspx .EXAMPLE Update-CodeObjectMetadata 'GetUsers' Demonstrates how to update the `GetUsers` code object. .EXAMPLE Update-CodeObjectMetadata -SchemaName 'example' 'GetUsers' Demonstrates how to update a code object in a custom schema, in this case the `example` schema. #> [CmdletBinding(DefaultParameterSetName='CodeObject')] param( [Parameter()] [string] # The code object's schema name. Default is `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=0)] [string] # The name of the code object. $Name, [Parameter(Mandatory=$true,ParameterSetName='DATABASE_DDL_TRIGGER')] [Switch] # The object is a database DDL trigger. $DatabaseDdlTrigger, [Parameter(Mandatory=$true,ParameterSetName='SERVER_DDL_TRIGGER')] [Switch] # The object is a server DDL trigger. $ServerDdlTrigger ) Set-StrictMode -Version 'Latest' $namespace = $null if( $PSCmdlet.ParameterSetName -like '*_DDL_TRIGGER' ) { $namespace = $PSCmdlet.ParameterSetName } New-Object 'Rivet.Operations.UpdateCodeObjectMetadataOperation' $SchemaName,$Name,$namespace } function Update-Description { <# .SYNOPSIS Updates the `MS_Description` extended property of a table or column. .DESCRIPTION The `sys.sp_updateextendedproperty` stored procedure is used to update a table/column's description (i.e. the `MS_Description` extended property), but the syntax is weird. This function hides that weirdness from you. You're welcome. .EXAMPLE Update-Description -Description 'Whoseit's whatsits table.' -TableName WhoseitsWhatsits Updates the description (i.e. the `MS_Description` extended property) on the `WhoseitsWhatsits` table. .EXAMPLE Update-Description -Description 'Is it a snarfblat?' -TableName WhoseitsWhatsits -ColumnName IsSnarfblat Updates the description (i.e. the `MS_Description` extended property) on the `WhoseitsWhatsits` table's `IsSnarfblat` column. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The value for the MS_Description extended property. $Description, [Alias('Schema')] [string] # The schema. Defaults to `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true)] [Alias('Table')] [string] # The name of the table where the extended property is getting updated. $TableName, [Parameter(ParameterSetName='ForColumn')] [Alias('Column')] [string] # The name of the column where the extended property is getting updated. $ColumnName ) Set-StrictMode -Version 'Latest' $optionalArgs = @{ } if( $ColumnName ) { $optionalArgs.ColumnName = $ColumnName } Update-ExtendedProperty -Name ([Rivet.Operations.ExtendedPropertyOperation]::DescriptionPropertyName) ` -Value $Description ` -SchemaName $SchemaName ` -TableName $TableName ` @optionalArgs } function Update-ExtendedProperty { <# .SYNOPSIS Updates an object's extended property. .DESCRIPTION SQL Server has a special stored procedure for updating extended property metatdata about an object. Unfortunately, it has a really clunky interface. This function is an attempt to wrap `sp_updateextendedproperty` with a saner interface. Currently, this function only supports updating properties for schemas, tables, and columns. Submit a patch! .LINK Add-Description .LINK Add-ExtendedProperty .LINK Remove-Description .LINK Remove-ExtendedProperty .LINK Update-Description .LINK Update-ExtendedProperty .EXAMPLE Update-ExtendedProperty -Name 'Deploy' -Value 'FALSE' -SchemaName 'spike' Sets the custom `Deploy` metadata to be `FALSE`. .EXAMPLE Update-ExtendedProperty -Name 'Deploy' -Value 'FALSE' -TableName 'Food' Sets the custom `Deploy` metadata to be `FALSE` on the `Food` table in the `dbo` schema. .EXAMPLE Update-ExtendedProperty -Name 'IsEncrypted' -Value 'TRUE' -TableName 'User' -ColumnName 'Password' Sets the custom `IsEncrypted` metadata to be `TRUE` on the `User` table's `Password` column. .EXAMPLE Update-ExtendedProperty -Name 'ContainsPII' -Value 'FALSE' -View 'LoggedInUsers' Demonstrates how to update custom metadata on the `LoggedInUsers` view .EXAMPLE Update-ExtendedProperty -Name 'IsEncrypted' -Value 'FALSE' -View 'LoggedInUsers' -ColumnName 'Password' Demonstrates how to update custom metadata for a view's column #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the extended property to update. $Name, [Parameter(Mandatory=$true,Position=1)] [AllowNull()] # The value of the extended property. $Value, [Parameter(ParameterSetName='SCHEMA')] [Parameter(ParameterSetName='TABLE')] [Parameter(ParameterSetName='TABLE-COLUMN')] [Parameter(ParameterSetName='VIEW')] [Parameter(ParameterSetName='VIEW-COLUMN')] [string] # The schema of the object. $SchemaName = 'dbo', [Parameter(Mandatory=$true,ParameterSetName='TABLE')] [Parameter(Mandatory=$true,ParameterSetName='TABLE-COLUMN')] [Alias('Table')] [string] # The table name. $TableName, [Parameter(Mandatory=$true,ParameterSetName='VIEW')] [Parameter(Mandatory=$true,ParameterSetName='VIEW-COLUMN')] [Alias('View')] [string] # The table name. $ViewName, [Parameter(Mandatory=$true,ParameterSetName='VIEW-COLUMN')] [Parameter(Mandatory=$true,ParameterSetName='TABLE-COLUMN')] [Alias('Column')] [string] # The column name. $ColumnName ) Set-StrictMode -Version 'Latest' $objectName = '' if ($PsCmdlet.ParameterSetName -eq "SCHEMA") { $op = New-Object 'Rivet.Operations.UpdateExtendedPropertyOperation' $SchemaName, $Name, $Value $objectName = $SchemaName } if ($PsCmdlet.ParameterSetName -eq "TABLE") { $op = New-Object 'Rivet.Operations.UpdateExtendedPropertyOperation' $SchemaName, $TableName, $Name, $Value, $false $objectName = '{0}.{1}' -f $SchemaName,$TableName } if ($PsCmdlet.ParameterSetName -eq "VIEW") { $op = New-Object 'Rivet.Operations.UpdateExtendedPropertyOperation' $SchemaName, $ViewName, $Name, $Value, $true $objectName = '{0}.{1}' -f $SchemaName,$ViewName } if ($PsCmdlet.ParameterSetName -eq "TABLE-COLUMN") { $op = New-Object 'Rivet.Operations.UpdateExtendedPropertyOperation' $SchemaName, $TableName, $ColumnName, $Name, $Value, $false $objectName = '{0}.{1}.{2}' -f $SchemaName,$TableName,$ColumnName } if ($PsCmdlet.ParameterSetName -eq "VIEW-COLUMN") { $op = New-Object 'Rivet.Operations.UpdateExtendedPropertyOperation' $SchemaName, $ViewName, $ColumnName, $Name, $Value, $true $objectName = '{0}.{1}.{2}' -f $SchemaName,$ViewName,$ColumnName } return $op } function Update-Row { <# .SYNOPSIS Updates a row of data in a table. .DESCRIPTION To specify which columns in a row to update, pass a hashtable as a value to the `Column` parameter. This hashtable should have keys that map to column names, and the value of each key will be used to update row(s) in the table. You are required to use a `Where` clause so that you don't inadvertently/accidentally update a column in every row in a table to the same value. If you *do* want to update the value in every row of the database, omit the `Where` parameter and add the `Force` switch. .EXAMPLE Update-Row -SchemaName 'rivet' 'Migrations' @{ LastUpdated = (Get-Date -Utc) } -Where 'MigrationID=20130913131104' Demonstrates how to update the `LastUpdated` date in the `rivet.Migrations` table for the migration with ID `20130913131104`. Don't do this in real life. .EXAMPLE Update-Row -SchemaName 'rivet' 'Migrations' @{ LastUpdated = (Get-Date -Utc) } -Force Demonstrates how to update the `LastUpdated` date *for all rows* in the `rivet.Migrations` table. You *really, really* don't want to do this in real life. .EXAMPLE Update-Row 'Migrations' @{ MigrationID = 'MigrationID + 1' } -Raw -Where 'MigrationID=20130101010101' Demonstrates how to pass a SQL expression as the value for the column to update: use the `-RawColumnValue` switch. #> param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the table. $TableName, [Parameter()] [string] # The schema name of the table. Default is `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1)] [Hashtable] # A hashtable of name/value pairs that map to column names/values that will be updated. $Column, [Switch] # Don't escape/quote the column value(s). $RawColumnValue, [Parameter(Mandatory=$true,Position=2,ParameterSetName='SpecificRows')] [string] # A condition to use so that only certain rows are updated. Without a value, you will need to use the `Force` parameter so you don't accidentally update the contents of an entire table. $Where, [Parameter(Mandatory=$true,ParameterSetName='AllRows')] [Switch] # Updates all the rows in the table. $All ) Set-StrictMode -Version 'Latest' if ($PSCmdlet.ParameterSetName -eq 'SpecificRows') { $op = New-Object 'Rivet.Operations.UpdateRowOperation' $SchemaName, $TableName, $Column, $Where, $RawColumnValue } elseif ($PSCmdlet.ParameterSetName -eq 'AllRows') { $op = New-Object 'Rivet.Operations.UpdateRowOperation' $SchemaName, $TableName, $Column, $RawColumnValue } return $op } function Update-StoredProcedure { <# .SYNOPSIS Updates an existing stored procedure. .DESCRIPTION Updates an existing stored procedure. .LINK https://msdn.microsoft.com/en-us/library/ms189762.aspx .EXAMPLE Update-StoredProcedure -SchemaName 'rivet' 'ReadMigrations' 'AS select * from rivet.Migrations' Updates a stored procedure to read the migrations from Rivet's Migrations table. Note that in real life, you probably should leave my table alone. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the stored procedure. $Name, [Parameter()] [string] # The schema name of the stored procedure. Defaults to `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1)] [string] # The store procedure's definition, which is everything after the `alter procedure [schema].[name]` clause. $Definition ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.UpdateStoredProcedureOperation' $SchemaName, $Name, $Definition } function Update-Table { <# .SYNOPSIS Adds new columns or alters existing columns on an existing table. .DESCRIPTION The `Update-Table` operation adds, updates, and removes columns from a table. Columns are added, then updated, then removed. The new columns for the table should be created and returned in a script block, which is passed as the value of the `AddColumn` parameter. For example, Update-Table 'Suits' -AddColumn { Bit 'HasVest' -NotNull -Default 0 } The new definitions for existing columns should be created and returned in a script block, which is passed as the value of the `UpdateColumn` parameter. For example, Update-Table 'Suits' -UpdateColumn { VarChar 'Color' 256 -NotNull } .LINK bigint .LINK binary .LINK bit .LINK char .LINK date .LINK datetime .LINK datetime2 .LINK datetimeoffset .LINK decimal .LINK float .LINK hierarchyid .LINK int .LINK money .LINK nchar .LINK numeric .LINK nvarchar .LINK real .LINK rowversion .LINK smalldatetime .LINK smallint .LINK smallmoney .LINK sqlvariant .LINK time .LINK tinyint .LINK uniqueidentifier .LINK varbinary .LINK varchar .LINK xml .EXAMPLE Update-Table -Name 'Ties' -AddColumn { VarChar 'Color' 50 -NotNull } Adds a new `Color` column to the `Ties` table. Pretty! .EXAMPLE Update-Table -Name 'Ties' -UpdateColumn { VarChar 'Color' 100 -NotNull } Demonstrates how to change the definition of an existing column. .EXAMPLE Update-Table -Name 'Ties' -RemoveColumn 'Pattern','Manufacturer' Demonstrates how to remove columns from a table. #> [CmdletBinding()] param( [Parameter(Mandatory,Position=0)] # The name of the table. [String]$Name, # The table's schema. Defaults to `dbo`. [String]$SchemaName = 'dbo', [Alias('Add')] # A script block that returns the new columns to add to a table. [scriptblock]$AddColumn, [Alias('Update')] [Alias('Alter')] # A script block that returns new column definitions for existing columns [scriptblock]$UpdateColumn, [Alias('Remove')] # Columns to remove. [String[]]$RemoveColumn ) Set-StrictMode -Version 'Latest' [Object[]]$newColumns = @() if( $AddColumn ) { $newColumns = & $AddColumn } [Object[]]$updatedColumns = @() if ($UpdateColumn) { $updatedColumns = & $UpdateColumn foreach( $column in $updatedColumns ) { if( $column.DefaultExpression -or $column.DefaultConstraintName ) { Write-Error -Message ("You're attempting to add a default constraint to existing column [$($column.Name)] on table [$($SchemaName)].[$($Name)]. SQL Server doesn't support adding default constraints on existing columns. Remove the -Default and -DefaultConstraintName parameters on this column and use the Add-DefaultConstraint operation to add a default constraint to this column.") -ErrorAction Stop return } if( $column.Identity ) { Write-Error -Message ("You're attempting to add identity to existing column [$($Column.Name)] on table [$($SchemaName)].[$($Name)]. This is not supported by SQL Server. You'll need to drop and re-create the column.") -ErrorAction Stop return } } } New-Object 'Rivet.Operations.UpdateTableOperation' $SchemaName,$Name,$newColumns,$updatedColumns,$RemoveColumn foreach ($i in $newColumns) { if ($i.Description) { Add-Description -Description $i.Description -SchemaName $SchemaName -TableName $Name -ColumnName $i.Name } } foreach ($i in $updatedColumns) { if ($i.Description) { Update-Description -Description $i.Description -SchemaName $SchemaName -TableName $Name -ColumnName $i.Name } } } function Update-Trigger { <# .SYNOPSIS Updates an existing trigger. .DESCRIPTION Updates an existing trigger. .LINK https://msdn.microsoft.com/en-us/library/ms176072.aspx .LINK Add-Trigger Remove-Trigger .EXAMPLE Update-Trigger 'PrintMessage' 'ON rivet.Migrations for insert as print ''Migration applied!''' Updates a trigger to prints a method when a row gets inserted into the `rivet.Migrations` table. #> param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the trigger. $Name, [Parameter()] [string] # The schema of the trigger. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1)] [string] # The body of the trigger. Everything after and including the `ON` clause. $Definition ) Set-StrictMode -Version 'Latest' New-Object 'Rivet.Operations.UpdateTriggerOperation' $SchemaName, $Name, $Definition } function Update-UserDefinedFunction { <# .SYNOPSIS Updates an existing user-defined function. .DESCRIPTION Updates an existing user-defined function. .LINK https://msdn.microsoft.com/en-us/library/ms186967.aspx .EXAMPLE Update-UserDefinedFunction -SchemaName 'rivet' 'One' 'returns tinyint begin return 1 end' Updates a user-defined function to return the number 1. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the stored procedure. $Name, [Parameter()] [string] # The schema name of the stored procedure. Defaults to `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1)] [string] # The store procedure's definition. Everything after the `alter function [schema].[name]` clause. $Definition ) New-Object Rivet.Operations.UpdateUserDefinedFunctionOperation $SchemaName,$Name,$Definition } function Update-View { <# .SYNOPSIS Updates an existing view. .DESCRIPTION Updates an existing view. .LINK https://msdn.microsoft.com/en-us/library/ms173846.aspx .EXAMPLE Update-View -SchemaName 'rivet' 'ReadMigrations' 'AS select * from rivet.Migrations' Updates a view to read all the migrations from Rivet's Migrations table. Don't do this in real life. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the view. $Name, [Parameter()] [string] # The schema name of the view. Defaults to `dbo`. $SchemaName = 'dbo', [Parameter(Mandatory=$true,Position=1)] [string] # The definition of the view. Everything after the `alter view [schema].[name]` clause. $Definition ) Set-StrictMode -Version 'Latest' New-Object Rivet.Operations.UpdateViewOperation $SchemaName,$Name,$Definition } |