【问题标题】:Create an SQL Backup without administrator priveleges创建没有管理员权限的 SQL 备份
【发布时间】:2021-05-09 01:55:03
【问题描述】:

我有一个备份 3 个 sql 数据库的脚本。

我在下面的两个代码块中附加了完整的脚本及其输出。不要被它们的大小吓倒,我只是在某个时候调用 powershell cmdlet Backup-SqlDatabase。

Backup-SqlDatabase documentation

https://docs.microsoft.com/en-us/powershell/module/sqlserver/backup-sqldatabase?view=sqlserver-ps

sqlServerInstanceName = 'localhost\SQLEXPRESS2014' 
$sqlUserName = 'SomeName'
$sqlPassword = 'SomePassword'
$secureString = ConvertTo-SecureString $sqlPassword -AsPlainText -Force

...

    # Backing up Sql database
    #

    # SQL cmdlets below need some dll imports from system
    # These do not get loaded sometime when running under an non-admin account (Cannof find a provider with the name 'SqlServer')
    # Running this dummy command seems to load all needed dlls
    # Also see: https://www.sqlservercentral.com/forums/topic/unable-access-sql-provider-in-powershell-without-running-an-invoke-sqlcmd-first
    Invoke-Sqlcmd | Out-Null

    # To use any SQL cmdlets we need to create a ps virtual drive with the right authenitcation
    # See: https://docs.microsoft.com/en-us/sql/powershell/manage-authentication-in-database-engine-powershell?view=sql-server-ver15#sql-server-authentication-using-a-virtual-drive
    # NOTE the above docs are a bit confusing, this seemed to work: https://social.technet.microsoft.com/Forums/en-US/f9901f20-01db-4d6e-bcfd-ecb5ca3ed64c/powershell-sqlserver-connect-via-newpsdrive?forum=winserverpowershell

    $sqlPsDrivename = 'sqlPsDrive'
    $sqlRoot = "SQLSERVER:\SQL\$sqlServerInstanceName"
    $sqlCred = new-object System.Management.Automation.PSCredential -ArgumentList $sqlUserName,$secureString
    
    if (Test-Path "$($sqlPsDrivename):")
    {
        Remove-PSDrive $sqlPsDrivename -PSProvider SqlServer -Scope 1 
    }
    
    $null = New-PSDrive $sqlPsDrivename -PSProvider SqlServer -Root $sqlRoot -Credential $sqlCred -Scope 1  
    
    $null = Set-Location "$($sqlPsDrivename):"

    [Microsoft.SqlServer.Management.Smo.Database[]] $allDatabases = Get-SqlDatabase -ServerInstance $sqlServerInstanceName 
    $databases = $allDatabases | Where-Object {$_.IsSystemObject -eq $false}

    if($databases -eq $null)
    {
        throw "No non-system databases were found in $sqlServerInstanceName"
    }

    $null = [System.IO.Directory]::CreateDirectory($sqlBackupDirectory)
    Write-Host Going to backup $databases.Count databases to $sqlBackupDirectory
    
    $databases | Backup-SqlDatabase -BackupContainer $sqlBackupDirectory -Verbose

    Write-Host Done with backing up $databases.Count databases to $sqlBackupDirectory  

这似乎可以正常工作,但是仅当我在 Windows 帐户 Administrator 下运行它时才有效。但是,当我在常规用户帐户下运行它时,例如Operator 失败并显示以下脚本输出:

Going to backup 3 databases to C:\ITM\FullBackup\Temp\Database
VERBOSE: Performing the operation "Backup-SqlDatabase" on target "[localhost\SQLEXPRESS2014]".
VERBOSE: 
        declare @HkeyLocal nvarchar(18)
        declare @ServicesRegPath nvarchar(34)
        declare @SqlServiceRegPath sysname
        declare @BrowserServiceRegPath sysname
        declare @MSSqlServerRegPath nvarchar(31)
        declare @InstanceNamesRegPath nvarchar(59)
        declare @InstanceRegPath sysname
        declare @SetupRegPath sysname
        declare @NpRegPath sysname
        declare @TcpRegPath sysname
        declare @RegPathParams sysname
        declare @FilestreamRegPath sysname

        select @HkeyLocal=N'HKEY_LOCAL_MACHINE'

        -- Instance-based paths
        select @MSSqlServerRegPath=N'SOFTWARE\Microsoft\MSSQLServer'
        select @InstanceRegPath=@MSSqlServerRegPath + N'\MSSQLServer'
        select @FilestreamRegPath=@InstanceRegPath + N'\Filestream'
        select @SetupRegPath=@MSSqlServerRegPath + N'\Setup'
        select @RegPathParams=@InstanceRegPath+'\Parameters'

        -- Services
        select @ServicesRegPath=N'SYSTEM\CurrentControlSet\Services'
        select @SqlServiceRegPath=@ServicesRegPath + N'\MSSQLSERVER'
        select @BrowserServiceRegPath=@ServicesRegPath + N'\SQLBrowser'

        -- InstanceId setting
        select @InstanceNamesRegPath=N'SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL'

        -- Network settings
        select @NpRegPath=@InstanceRegPath + N'\SuperSocketNetLib\Np'
        select @TcpRegPath=@InstanceRegPath + N'\SuperSocketNetLib\Tcp'
      


        declare @SmoAuditLevel int
        exec master.dbo.xp_instance_regread @HkeyLocal, @InstanceRegPath, N'AuditLevel', @SmoAuditLevel OUTPUT
      


        declare @NumErrorLogs int
        exec master.dbo.xp_instance_regread @HkeyLocal, @InstanceRegPath, N'NumErrorLogs', @NumErrorLogs OUTPUT
      


        declare @SmoLoginMode int
        exec master.dbo.xp_instance_regread @HkeyLocal, @InstanceRegPath, N'LoginMode', @SmoLoginMode OUTPUT
      


        declare @SmoMailProfile nvarchar(512)
        exec master.dbo.xp_instance_regread @HkeyLocal, @InstanceRegPath, N'MailAccountName', @SmoMailProfile OUTPUT
      


        declare @BackupDirectory nvarchar(512)
        if 1=isnull(cast(SERVERPROPERTY('IsLocalDB') as bit), 0)
          select @BackupDirectory=cast(SERVERPROPERTY('instancedefaultdatapath') as nvarchar(512))
        else
          exec master.dbo.xp_instance_regread @HkeyLocal, @InstanceRegPath, N'BackupDirectory', @BackupDirectory OUTPUT
      


        declare @SmoPerfMonMode int
        exec master.dbo.xp_instance_regread @HkeyLocal, @InstanceRegPath, N'Performance', @SmoPerfMonMode OUTPUT

        if @SmoPerfMonMode is null
        begin
        set @SmoPerfMonMode = 1000
        end
      


        declare @InstallSqlDataDir nvarchar(512)
        exec master.dbo.xp_instance_regread @HkeyLocal, @SetupRegPath, N'SQLDataRoot', @InstallSqlDataDir OUTPUT
      


        declare @MasterPath nvarchar(512)
        declare @LogPath nvarchar(512)
        declare @ErrorLog nvarchar(512)
        declare @ErrorLogPath nvarchar(512)

        select @MasterPath=substring(physical_name, 1, len(physical_name) - charindex('\', reverse(physical_name))) from master.sys.database_files where name=N'master'
        select @LogPath=substring(physical_name, 1, len(physical_name) - charindex('\', reverse(physical_name))) from master.sys.database_files where name=N'mastlog'
        select @ErrorLog=cast(SERVERPROPERTY(N'errorlogfilename') as nvarchar(512))
        select @ErrorLogPath=substring(@ErrorLog, 1, len(@ErrorLog) - charindex('\', reverse(@ErrorLog)))
      


        declare @SmoRoot nvarchar(512)
        exec master.dbo.xp_instance_regread @HkeyLocal, @SetupRegPath, N'SQLPath', @SmoRoot OUTPUT
      


        declare @ServiceStartMode int
        EXEC master.sys.xp_instance_regread @HkeyLocal, @SqlServiceRegPath, N'Start', @ServiceStartMode OUTPUT
      


        declare @ServiceAccount nvarchar(512)
        EXEC master.sys.xp_instance_regread @HkeyLocal, @SqlServiceRegPath, N'ObjectName', @ServiceAccount OUTPUT
      


        declare @NamedPipesEnabled int
        exec master.dbo.xp_instance_regread @HkeyLocal, @NpRegPath, N'Enabled', @NamedPipesEnabled OUTPUT
      


        declare @TcpEnabled int
        EXEC master.sys.xp_instance_regread @HkeyLocal, @TcpRegPath, N'Enabled', @TcpEnabled OUTPUT
      


        declare @InstallSharedDirectory nvarchar(512)
        EXEC master.sys.xp_instance_regread @HkeyLocal, @SetupRegPath, N'SQLPath', @InstallSharedDirectory OUTPUT
      


        declare @SqlGroup nvarchar(512)
        exec master.dbo.xp_instance_regread @HkeyLocal, @SetupRegPath, N'SQLGroup', @SqlGroup OUTPUT
      


        declare @FilestreamLevel int
        exec master.dbo.xp_instance_regread @HkeyLocal, @FilestreamRegPath, N'EnableLevel', @FilestreamLevel OUTPUT
      


        declare @FilestreamShareName nvarchar(512)
        exec master.dbo.xp_instance_regread @HkeyLocal, @FilestreamRegPath, N'ShareName', @FilestreamShareName OUTPUT
      


        declare @cluster_name nvarchar(128)
        declare @quorum_type tinyint
        declare @quorum_state tinyint
        BEGIN TRY
            SELECT @cluster_name = cluster_name, 
                @quorum_type = quorum_type,
                @quorum_state = quorum_state
            FROM sys.dm_hadr_cluster
        END TRY
        BEGIN CATCH
            IF(ERROR_NUMBER() NOT IN (297,300))
            BEGIN
                THROW
            END
        END CATCH
      

SELECT
@SmoAuditLevel AS [AuditLevel],
ISNULL(@NumErrorLogs, -1) AS [NumberOfLogFiles],
(case when @SmoLoginMode < 3 then @SmoLoginMode else 9 end) AS [LoginMode],
ISNULL(@SmoMailProfile,N'') AS [MailProfile],
@BackupDirectory AS [BackupDirectory],
@SmoPerfMonMode AS [PerfMonMode],
ISNULL(@InstallSqlDataDir,N'') AS [InstallDataDirectory],
CAST(@@SERVICENAME AS sysname) AS [ServiceName],
@ErrorLogPath AS [ErrorLogPath],
@SmoRoot AS [RootDirectory],
CAST(case when 'a' <> 'A' then 1 else 0 end AS bit) AS [IsCaseSensitive],
@@MAX_PRECISION AS [MaxPrecision],
CAST(FULLTEXTSERVICEPROPERTY('IsFullTextInstalled') AS bit) AS [IsFullTextInstalled],
SERVERPROPERTY(N'ProductVersion') AS [VersionString],
CAST(SERVERPROPERTY(N'Edition') AS sysname) AS [Edition],
CAST(SERVERPROPERTY(N'ProductLevel') AS sysname) AS [ProductLevel],
CAST(SERVERPROPERTY('IsSingleUser') AS bit) AS [IsSingleUser],
CAST(SERVERPROPERTY('EngineEdition') AS int) AS [EngineEdition],
convert(sysname, serverproperty(N'collation')) AS [Collation],
CAST(SERVERPROPERTY('IsClustered') AS bit) AS [IsClustered],
CAST(SERVERPROPERTY(N'MachineName') AS sysname) AS [NetName],
@LogPath AS [MasterDBLogPath],
@MasterPath AS [MasterDBPath],
SERVERPROPERTY('instancedefaultdatapath') AS [DefaultFile],
SERVERPROPERTY('instancedefaultlogpath') AS [DefaultLog],
SERVERPROPERTY(N'ResourceVersion') AS [ResourceVersionString],
SERVERPROPERTY(N'ResourceLastUpdateDateTime') AS [ResourceLastUpdateDateTime],
SERVERPROPERTY(N'CollationID') AS [CollationID],
SERVERPROPERTY(N'ComparisonStyle') AS [ComparisonStyle],
SERVERPROPERTY(N'SqlCharSet') AS [SqlCharSet],
SERVERPROPERTY(N'SqlCharSetName') AS [SqlCharSetName],
SERVERPROPERTY(N'SqlSortOrder') AS [SqlSortOrder],
SERVERPROPERTY(N'SqlSortOrderName') AS [SqlSortOrderName],
SERVERPROPERTY(N'ComputerNamePhysicalNetBIOS') AS [ComputerNamePhysicalNetBIOS],
SERVERPROPERTY(N'BuildClrVersion') AS [BuildClrVersionString],
@ServiceStartMode AS [ServiceStartMode],
ISNULL(@ServiceAccount,N'') AS [ServiceAccount],
CAST(@NamedPipesEnabled AS bit) AS [NamedPipesEnabled],
CAST(@TcpEnabled AS bit) AS [TcpEnabled],
ISNULL(@InstallSharedDirectory,N'') AS [InstallSharedDirectory],
ISNULL(suser_sname(sid_binary(ISNULL(@SqlGroup,N''))),N'') AS [SqlDomainGroup],
case when 1=msdb.dbo.fn_syspolicy_is_automation_enabled() and exists (select * from msdb.dbo.syspolicy_system_health_state  where target_query_expression_with_id like 'Server%' ) then 1 else 
0 end AS [PolicyHealthState],
@FilestreamLevel AS [FilestreamLevel],
ISNULL(@FilestreamShareName,N'') AS [FilestreamShareName],
-1 AS [TapeLoadWaitTime],
CAST(SERVERPROPERTY(N'IsHadrEnabled') AS bit) AS [IsHadrEnabled],
SERVERPROPERTY(N'HADRManagerStatus') AS [HadrManagerStatus],
ISNULL(@cluster_name, '') AS [ClusterName],
ISNULL(@quorum_type, 4) AS [ClusterQuorumType],
ISNULL(@quorum_state, 3) AS [ClusterQuorumState],
SUSER_SID(@ServiceAccount, 0) AS [ServiceAccountSid],
CAST(
        serverproperty(N'Servername')
       AS sysname) AS [Name],
CAST(
        ISNULL(serverproperty(N'instancename'),N'')
       AS sysname) AS [InstanceName],
CAST(0x0001 AS int) AS [Status],
0 AS [IsContainedAuthentication],
CAST(null AS int) AS [ServerType]
VERBOSE: RegQueryValueEx() returned error 2, 'The system cannot find the file specified.'
VERBOSE: RegQueryValueEx() returned error 2, 'The system cannot find the file specified.'
VERBOSE: 
        declare @HkeyLocal nvarchar(18)
        declare @ServicesRegPath nvarchar(34)
        declare @SqlServiceRegPath sysname
        declare @BrowserServiceRegPath sysname
        declare @MSSqlServerRegPath nvarchar(31)
        declare @InstanceNamesRegPath nvarchar(59)
        declare @InstanceRegPath sysname
        declare @SetupRegPath sysname
        declare @NpRegPath sysname
        declare @TcpRegPath sysname
        declare @RegPathParams sysname
        declare @FilestreamRegPath sysname

        select @HkeyLocal=N'HKEY_LOCAL_MACHINE'

        -- Instance-based paths
        select @MSSqlServerRegPath=N'SOFTWARE\Microsoft\MSSQLServer'
        select @InstanceRegPath=@MSSqlServerRegPath + N'\MSSQLServer'
        select @FilestreamRegPath=@InstanceRegPath + N'\Filestream'
        select @SetupRegPath=@MSSqlServerRegPath + N'\Setup'
        select @RegPathParams=@InstanceRegPath+'\Parameters'

        -- Services
        select @ServicesRegPath=N'SYSTEM\CurrentControlSet\Services'
        select @SqlServiceRegPath=@ServicesRegPath + N'\MSSQLSERVER'
        select @BrowserServiceRegPath=@ServicesRegPath + N'\SQLBrowser'

        -- InstanceId setting
        select @InstanceNamesRegPath=N'SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL'

        -- Network settings
        select @NpRegPath=@InstanceRegPath + N'\SuperSocketNetLib\Np'
        select @TcpRegPath=@InstanceRegPath + N'\SuperSocketNetLib\Tcp'
      


        declare @ServiceInstanceId nvarchar(512)
        EXEC master.sys.xp_regread @HkeyLocal, @InstanceNamesRegPath, @@SERVICENAME, @ServiceInstanceId OUTPUT
      

SELECT
ISNULL(@ServiceInstanceId,N'') AS [ServiceInstanceId]
D:\Projects\Hmi.Ushape\DistributedFiles\FullBackup\FullBackup.ps1 : An exception occurred while executing a Transact-SQL statement or batch.
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,FullBackup.ps1
 
Script end.

在这两种情况下,我都使用 SQL 服务器帐户而不是 Windows 帐户进行连接。此帐户拥有 3 个数据库的 DBO 权限 - 所以在我看来,它应该能够始终创建备份。

在Backup-SqlDatabase 命令的详细日志记录中,您可以看到: VERBOSE: RegQueryValueEx() returned error 2, 'The system cannot find the file specified.'。我还看到它显然试图从HKLM 读取,这当然只能在管理员帐户下访问,甚至可能是提升权限。

是否有另一种方法可以让我不必在管理员帐户下运行来创建备份(或复制 .mdf/.ldf 文件,无需 sql 锁定文件)?我只有一个拥有这 3 个数据库所有权的 sql 帐户和一个用户 windows 帐户。

【问题讨论】:

  • 问题代码较多,调试困难。尝试创建一个simple example;这可能会帮助您找出代码的哪一部分首先不起作用。
  • @vonPryz 你好,第二部分只是调试输出。我尝试将其放入扰流板 (&lt;summary&gt;) 中,但除此之外没有用 - 脚本已经是最小的可重现样本。
  • 您在那里使用了大量未记录的扩展程序。谁知道他们的要求是什么?纯粹从备份的角度来看,执行备份的 SQL 登录名/帐户需要 db_backupoperator 角色,备份的目标文件夹需要修改 SQL Server 本身正在执行的帐户的权限。
  • 虽然最终要求可能是备份数据库,但详细输出会执行备份数据库不需要的各种特权操作,例如使用 xp_instance_regread 读取注册表。我不知道那个 powershell 命令是如何产生所有输出的。也许这对Backup-SqlDatabase 来说是正常的
  • 100% 使用 SQL 方法。看起来那些注册表读取正在尝试计算默认备份目录,但那里有一大堆其他没有相关性的操作。

标签: sql-server powershell sql-server-2014


【解决方案1】:

我的回答不会解释如何在 PowerShell 中使用 Backup-SqlDatabase,也不会解释如何配置运行此 cmdlet 所需的权限。

当我想使用 PowerShell 脚本备份数据库时,我没有使用 Backup-SqlDatabase cmdlet。我更喜欢编写原生 SQL BACKUP DATABASE 命令。

所以,我创建了两个文件。一个是 PowerShell 脚本,另一个是 SQL 脚本。

make_backup.sql 有一个BACKUP DATABASE 命令:

BACKUP DATABASE [DBName] TO DISK = N'D:\BACKUP\DBNameFull.bak' WITH 
    FORMAT, INIT, NAME = N'DBName-Full Database Backup', 
    SKIP, NOREWIND, NOUNLOAD, COMPRESSION, CHECKSUM

根据需要调整选项。

make_backup.ps1使用sqlcmd执行SQL脚本:

& sqlcmd -S DBName -U UserName -P UserPassword -i "make_backup.sql"

【讨论】:

  • 嗨弗拉基米尔,感谢您发布答案。我将尝试运行它,看看它是否效果更好。只是一些后续问题:a)您以前是否在常规的非管理员 Windows 用户帐户下看到过这项工作? b) 你能提供一些关于你为什么使用这些选项的说明吗?为什么它们很方便/必要?我一定会在一分钟内查看这些选项的文档。
  • @sommmen,是的,我在普通用户下运行这个 PowerShell 脚本。手动和通过任务计划程序。该用户拥有备份所在文件夹的完全权限。 BACKUP DATABASE 的选项?我使用 SSMS 生成脚本(当您使用 SSMS GUI 手动备份数据库时,有一个“生成脚本”按钮)。我以它为起点,根据自己的喜好进行调整。
  • 测试了这种方法,它可以正常工作。谢谢!
【解决方案2】:

为了完成答案,我放弃了Backup-SqlDatabase 的使用,并按如下方式编辑了我的脚本:

备份数据库.sql

BACKUP DATABASE [$(dbName)] TO DISK = N'$(filePath)' WITH 
    FORMAT, INIT, NAME = N'$(dbName)-Full Database Backup', 
    SKIP, NOREWIND, NOUNLOAD, CHECKSUM

FullBackup.ps1

function Backup-SingleDatabase
{
    Param(

        [string] $dbName,
        [string] $filePath

    )
        
    Write-Host Backing up $dbName to $filePath

    $SqlcmdVariables= @(
        "dbName=$dbName",
        "filePath=$filePath"
    )

    Invoke-Sqlcmd -InputFile $BackupDatabaseSqlFilePath -Database $dbName -Variable $SqlcmdVariables -OutputSqlErrors $true -SuppressProviderContextWarning
}

...

# Backing up Sql database
#

# SQL cmdlets below need some dll imports from system
# These do not get loaded sometime when running under an non-admin account (Cannof find a provider with the name 'SqlServer')
# Running this dummy command seems to load all needed dlls
# Also see: https://www.sqlservercentral.com/forums/topic/unable-access-sql-provider-in-powershell-without-running-an-invoke-sqlcmd-first
Invoke-Sqlcmd | Out-Null

# To use any SQL cmdlets we need to create a ps virtual drive with the right authenitcation
# See: https://docs.microsoft.com/en-us/sql/powershell/manage-authentication-in-database-engine-powershell?view=sql-server-ver15#sql-server-authentication-using-a-virtual-drive
# NOTE the above docs are a bit confusing, this seemed to work: https://social.technet.microsoft.com/Forums/en-US/f9901f20-01db-4d6e-bcfd-ecb5ca3ed64c/powershell-sqlserver-connect-via-newpsdrive?forum=winserverpowershell

$sqlPsDrivename = 'sqlPsDrive'
$sqlRoot = "SQLSERVER:\SQL\$sqlServerInstanceName"
$secureString = ConvertTo-SecureString $sqlPassword -AsPlainText -Force
$sqlCred = new-object System.Management.Automation.PSCredential -ArgumentList $sqlUserName,$secureString

if (Test-Path "$($sqlPsDrivename):")
{
    Remove-PSDrive $sqlPsDrivename -PSProvider SqlServer -Scope 1 
}

$null = New-PSDrive $sqlPsDrivename -PSProvider SqlServer -Root $sqlRoot -Credential $sqlCred -Scope 1  

$null = Set-Location "$($sqlPsDrivename):"

[Microsoft.SqlServer.Management.Smo.Database[]] $allDatabases = Get-SqlDatabase -ServerInstance $sqlServerInstanceName 
$databases_ = $allDatabases | Where-Object {$_.IsSystemObject -eq $false}

if($databases_ -eq $null)
{
    throw "No non-system databases were found in $sqlServerInstanceName"
}

[Microsoft.SqlServer.Management.Smo.Database[]] $databases = $databases_

$null = [System.IO.Directory]::CreateDirectory($sqlBackupDirectory)
Write-Host Going to backup $databases.Count databases to $sqlBackupDirectory

$databases | % {Backup-SingleDatabase -dbName "$($_.Name)" -filePath "$sqlBackupDirectory\$($_.Name)_$($hostName)_$(get-date -f yyyyMMdd-HHmmss).bak"}

# Cleaning up after we're done
Pop-Location

以下文章有助于将参数传递给 sql 脚本: https://www.dbbest.com/blog/using-powershell-invoke-sqlcmd-with-variable/

如果我要重做这个,我可能会将所有逻辑放在.sql 文件中并调用它。现在我在 .ps1 文件中有一些关于获取服务器中所有用户数据库的逻辑,然后我为每个数据库调用我的 .sql 脚本。不过,我现在懒得重写它了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-23
    • 1970-01-01
    • 2019-04-02
    • 2012-03-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-23
    • 1970-01-01
    相关资源
    最近更新 更多