使用PowerShell脚本复制SQL Server数据库

时间:2018-08-06 14:55:25

标签: sql-server powershell

我想使用下面的代码在同一台服务器中复制数据库以使用测试数据库,但是在第一次运行时工作正常,然后出现错误。我认为这是目标数据库名称的问题,因为我更改了目标名称也可以使用。如何在不重命名目标的情况下覆盖目标数据库。

 Import-Module SQLPS -DisableNameChecking

        #your SQL Server Instance Name
        $SQLInstanceName = "DESKTOP-444"
        $Server  = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server -ArgumentList $SQLInstanceName

        #provide your database name which you want to copy
        $SourceDBName   = "test"

        #create SMO handle to your database
        $SourceDB = $Server.Databases[$SourceDBName]

        #create a database to hold the copy of your source database
        $CopyDBName = "$($SourceDBName)_copy"
        $CopyDB = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Database -ArgumentList $Server , $CopyDBName

    $CopyDB.Create()

    #Use SMO Transfer Class by specifying source database
    #you can specify properties you want either brought over or excluded, when the copy happens
    $ObjTransfer   = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Transfer -ArgumentList $SourceDB
    $ObjTransfer.CopyAllTables = $true
    $ObjTransfer.Options.WithDependencies = $true
    $ObjTransfer.Options.ContinueScriptingOnError = $true
    $ObjTransfer.DestinationDatabase = $CopyDBName
    $ObjTransfer.DestinationServer = $Server.Name
    $ObjTransfer.DestinationLoginSecure = $true
    $ObjTransfer.CopySchema = $true

    #if you wish to just generate the copy script
    #just script out the transfer
    $ObjTransfer.ScriptTransfer()

    #When you are ready to bring the data and schema over,
    #you can use the TransferData method
    $ObjTransfer.TransferData()

2 个答案:

答案 0 :(得分:1)

编辑

有人告诉我使用模块SqlServer而不是模块SQLPS,因为长期以来不推荐使用后者。而且,在进行更改后,我立即注意到可以从以前没有管理的Microsoft.SqlServer.Management.SMO.Transfer对象创建数据库。我不明白为什么,它甚至可能无关,我很幸运。可以通过以下命令安装SqlServer软件包:

Install-Module -Name SqlServer -AllowClobber

因此,我正在使用工作代码更新我的答案,该代码比我以前的答案(在本文的底部)更具可读性,更优雅,更高效。

$SQLInstanceName = $env:servername
$SourceDBName = $env:databasename
$SQLUser = $env:adminlogin
$SQLPassword = $env:adminPassword

Import-Module SqlServer -DisableNameChecking
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null;

Function IsNullOrEmpty([string]$val){
    if ($val -eq $null -or $val -eq '') { $true }
    else{ $false }
}

If (IsNullOrEmpty($SQLInstanceName)) {
    $SQLInstanceName = $args[0]
}

If (IsNullOrEmpty($SourceDBName)) {
    $SourceDBName = $args[1]
}

If (IsNullOrEmpty($SQLUser)) {
    $SQLUser = $args[2]
}

If (IsNullOrEmpty($SQLPassword)) {
    $SQLPassword = $args[3]
}


Try {
    $Server = New-Object Microsoft.SqlServer.Management.Smo.Server($SQLInstanceName)

    $DestinationDBName = "${SourceDBName}.Staging"
    $SQLSecurePassword = ConvertTo-SecureString $SQLPassword -AsPlainText -Force

    $Server.ConnectionContext.LoginSecure = $false
    $Server.ConnectionContext.set_Login($SQLUser)
    $Server.ConnectionContext.set_SecurePassword($SQLSecurePassword)

    $SourceDB = $Server.Databases[$SourceDBName]
    $ObjTransfer = New-Object Microsoft.SqlServer.Management.SMO.Transfer ($SourceDB)

    $CopyDB = New-Object Microsoft.SqlServer.Management.SMO.Database ($Server, $DestinationDBName)
    $CopyDB.Create()


    # $ObjTransfer.CopyData = $false  - Uncomment this line so that data is not copied across
    $ObjTransfer.CopySchema = $true
    $ObjTransfer.CopyAllTables = $true
    $ObjTransfer.CopyAllDatabaseTriggers = $true
    $ObjTransfer.Options.WithDependencies = $true  
    $ObjTransfer.Options.ContinueScriptingOnError = $true  
    $ObjTransfer.DestinationDatabase = $DestinationDBName  
    $ObjTransfer.DestinationServer = $SQLInstanceName
    $ObjTransfer.DestinationPassword = $SQLPassword
    $ObjTransfer.DestinationLogin = $SQLUser
    $ObjTransfer.DestinationLoginSecure = $false
    $ObjTransfer.TransferData()
}
Catch [System.Exception] {
    # $_ is set to the ErrorRecord of the exception
    if ($_.Exception.InnerException) {
        Write-Error $_.Exception.InnerException.Message
    } else {
        Write-Error $_.Exception.Message
    }

    if($Server.Databases.Name -like $DestinationDBName) {
        Write-Host "Dropping cloned database..."

        # Call drop-db.ps1 to delete the stagingDB
        Invoke-Command { .\drop-db.ps1 $SQLInstanceName $DestinationDBName $SQLUser $SQLPassword }
    }
}
Finally {
    if($Server) {
        $Server.ConnectionContext.Disconnect()
    }
}

我在执行此操作时遇到类似的错误。试了所有的东西,只是行不通。对我有用的是通过ScriptTransfer方法生成脚本,创建新数据库,然后通过Invoke-SqlCmd将脚本应用于新数据库。通过按以下顺序向脚本传递4个参数,可以在本地调用我共享的代码:

  1. 服务器名称
  2. 数据库名称
  3. 登录
  4. 密码

它也可以在管道上使用。我在setting those 4 arguments through a group variable的Azure DevOps上使用它。

我要在源数据库名称后附加.Staging,这就是我为新数据库指定的名称。如果在此过程中出现故障,我将删除新数据库(如果已创建)。

$SQLInstanceName = $env:servername
$SourceDBName = $env:databasename
$SQLUser = $env:adminlogin
$SQLPassword = $env:adminPassword

Import-Module SQLPS -DisableNameChecking
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null;

Function IsNullOrEmpty([string]$val){
    if ($val -eq $null -or $val -eq '') { $true }
    else{ $false }
}

If (IsNullOrEmpty($SQLInstanceName)) {
    $SQLInstanceName = $args[0]
}

If (IsNullOrEmpty($SourceDBName)) {
    $SourceDBName = $args[1]
}

If (IsNullOrEmpty($SQLUser)) {
    $SQLUser = $args[2]
}

If (IsNullOrEmpty($SQLPassword)) {
    $SQLPassword = $args[3]
}

Try {
    $Server = New-Object Microsoft.SqlServer.Management.Smo.Server($SQLInstanceName)
}
Catch [System.Exception] {
    # $_ is set to the ErrorRecord of the exception
    if ($_.Exception.InnerException) {
        Write-Error $_.Exception.InnerException.Message
    } else {
        Write-Error $_.Exception.Message
    }
}
Finally {

    Try {
        $StagingDBName      = "${SourceDBName}.Staging"
        $SQLSecurePassword  = ConvertTo-SecureString $SQLPassword -AsPlainText -Force

        $Server.ConnectionContext.LoginSecure = $false
        $Server.ConnectionContext.set_Login($SQLUser)
        $Server.ConnectionContext.set_SecurePassword($SQLSecurePassword)

        $CreationScriptOptions = New-Object Microsoft.SqlServer.Management.SMO.ScriptingOptions
        $CreationScriptOptions.ExtendedProperties= $true
        $CreationScriptOptions.DRIAll= $true
        $CreationScriptOptions.Indexes= $true
        $CreationScriptOptions.Triggers= $true            $CreationScriptOptions.ScriptBatchTerminator = $true
        $CreationScriptOptions.IncludeHeaders = $true;
        $CreationScriptOptions.ToFileOnly = $true
        $CreationScriptOptions.IncludeIfNotExists = $true     
        $SourceDB = $Server.Databases[$SourceDBName]
        $ObjTransfer = New-Object Microsoft.SqlServer.Management.SMO.Transfer ($SourceDB)
        $ObjTransfer.options=$CreationScriptOptions # tell the transfer object of our preferences

        $FilePath = Join-Path $PSScriptRoot "$($StagingDBName).sql"
        $ObjTransfer.Options.Filename = $FilePath; 
        $ObjTransfer.ScriptTransfer()

        $CopyDB = New-Object Microsoft.SqlServer.Management.SMO.Database ($Server, $StagingDBName)
        $CopyDB.Create()

        $auth=@{UserName=$SQLUser;Password=$SQLPassword}
        Invoke-SqlCmd -InputFile $FilePath -ServerInstance $Server -Database $StagingDBName @Auth -Verbose
    }
    Catch [System.Exception] {
        # $_ is set to the ErrorRecord of the exception
        if ($_.Exception.InnerException) {
            Write-Error $_.Exception.InnerException.Message
        } else {
            Write-Error $_.Exception.Message
        }

        if($Server.Databases.Name -like $StagingDBName) {
            Write-Host "Dropping staging database..."

            $auth=@{UserName=$SQLUser;Password=$SQLPassword}
            Invoke-SqlCmd -ServerInstance $Server @Auth `
                -Query "IF EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name ='$($StagingDBName)') `
                            BEGIN `
                                ALTER DATABASE [$($StagingDBName)] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; `
                                DROP DATABASE [$($StagingDBName)]; `
                            END;" `
                -Verbose
        }
    }
    Finally {
        $Server.ConnectionContext.Disconnect()
    }
}

答案 1 :(得分:0)

我能够多次运行您的代码而没有任何问题。以下是稍作清理的版本(结构更改):

Import-Module SQLPS -DisableNameChecking

$SQLInstanceName = "(local)"
$SourceDBName   = "sandbox"
$CopyDBName = "${SourceDBName}_copy"

$Server  = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' -ArgumentList $SQLInstanceName
$SourceDB = $Server.Databases[$SourceDBName]
$CopyDB = New-Object -TypeName 'Microsoft.SqlServer.Management.SMO.Database' -ArgumentList $Server , $CopyDBName
$CopyDB.Create()

$ObjTransfer   = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Transfer -ArgumentList $SourceDB
$ObjTransfer.CopyAllTables = $true
$ObjTransfer.Options.WithDependencies = $true
$ObjTransfer.Options.ContinueScriptingOnError = $true
$ObjTransfer.DestinationDatabase = $CopyDBName
$ObjTransfer.DestinationServer = $Server.Name
$ObjTransfer.DestinationLoginSecure = $true
$ObjTransfer.CopySchema = $true

$ObjTransfer.ScriptTransfer()
$ObjTransfer.TransferData()

您遇到什么错误?

我注意到的一件事。如果克隆的数据库已经存在,则脚本将失败。将对象复制到克隆数据库时,应该在$CopyDB.Create()语句周围出现异常,并且可能还会出现另一个异常。

我要么删除数据库(如果存在),要么中止脚本执行(如果存在)。