在 SQL Server 代理中运行 Powershell 脚本时出现执行策略错误

Cra*_*ein 7 sql-server permissions powershell sql-server-agent sql-server-2014

在 2014 年使用我的 AD 帐户通过凭据从 SQL Server 代理运行 powershell 脚本。我收到以下错误。

作业步骤在 PowerShell 脚本的第 1 行收到错误。相应的行是“set-executionpolicy RemoteSigned -scope process -Force”。更正脚本并重新安排作业。PowerShell 返回的错误信息是:'Security error.

我在谷歌上搜索,没有发现任何有用的东西。我可以在我的工作站上通过 SSMS 从 Powershell 控制台运行脚本,没有任何问题。

执行策略设置为不受限制

PS C:\WINDOWS\system32> Get-ExecutionPolicy
Unrestricted
Run Code Online (Sandbox Code Playgroud)

错误输出中提到的行必须由 SQL Server 自动添加,因为RemoteSigned -scope process -Force它不在代码中的任何位置。

除了使用 AD 帐户运行作业之外,我还需要在 SQL Server 代理中设置什么吗?

这是来自的powershell行 msdb.dbo.syssubsystems

C:\Program Files (x86)\Microsoft SQL Server\120\Tools\Binn\SQLPS.exe

更新

这是版本

PS SQLSERVER:\SQL\CD000023\CEF_2014_1> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
2      0      -1     -1
Run Code Online (Sandbox Code Playgroud)

更新 01/03/2015

此脚本基于中央管理服务器的注册服务器创建表 serverlist。然后它连接到这些服务器中的每一个并识别其侦听的端口。

# connection parameters
 Param ( 
      [string] $CMSServer="someuser\someinstance",  # CMS server that stores serverlist
      [string] $CMSDatabase="msdb",                 # database where the serverlist is stored
      [string] $CMSUser="someuser",         # username to connect to the cms server
      [string] $CMSPassword="somepassword",     # password to connect with the cmsuser
      [string] $CMSTable="dbo.serverlist",          # name of table that stores instances
      [string] $CMSTableNoSchema="serverlist",      # name of table that stores instances
      [string] $UserName="remoteuser",              # username to connect to each instance
      [string] $Password="remotepassword",      # password to connect to each instance
      [string] $SrcDatabase="tempdb",               # database where listening ports are stored
      [string] $SrcTable="#listeningport"           # table where listening ports are stored


 )

 # load in the SQL Server Powershell Module
 [System.Reflection.Assembly]::LoadWithPartialName( `
  "Microsoft.SqlServer.Smo");


 # log file function
 $logfile = "c:\temp\get_server_ports_$(get-date -format `"yyyy_MM_ddtt`").txt"
 # initalize log file
 $logfile | out-file -Filepath $logfile 

  function log($string, $color)
{
   if ($Color -eq $null) {$color = "white"}
   write-host $string -foregroundcolor $color
   $string | out-file -Filepath $logfile -append
}

# CMS Server connection 
$CMSServerConnectionString = "Data Source=$CMSServer;Initial Catalog=$CMSDatabase;User Id=$CMSUser;PWD=$CMSPassword;"
$CMSServerConnection = new-object system.data.SqlClient.SqlConnection($CMSServerConnectionString);
$CMSServerConnection.Open() 

# create SMO objects so that tables can be created and dropped
$srv = new-Object Microsoft.SqlServer.Management.Smo.Server($CMSServerConnection)
$db = New-Object Microsoft.SqlServer.Management.Smo.Database
$db = $srv.Databases.Item($CMSDatabase)

# drop and recreate the serverlist Table on the CMS server
$tb = $db.Tables[$CMSTableNoSchema]
IF ($tb)
    {$tb.Drop()}

# Create the serverlist Table on the cms server
$tb = new-object Microsoft.SqlServer.Management.Smo.Table($db, $CMSTableNoSchema)
$col1 = new-object Microsoft.SqlServer.Management.Smo.Column($tb, "server_name", [Microsoft.SqlServer.Management.Smo.DataType]::NChar(255))
$col2 = new-object Microsoft.SqlServer.Management.Smo.Column($tb, "server_port", [Microsoft.SqlServer.Management.Smo.DataType]::Int)
$tb.Columns.Add($col1)
$tb.Columns.Add($col2)
$tb.Create()

# collect the list of servers
$cmd4 = new-object System.Data.SQLClient.SQLCommand
$cmd4.CommandText = "
    insert into msdb.dbo.serverlist (server_name, server_port)
    select server_name, 1 from msdb.dbo.sysmanagement_shared_registered_servers_internal
"
$cmd4.Connection = $CMSServerConnection
$rowsInserted = $cmd4.ExecuteNonQuery()


# Create a Dataset to hold the DataTable from server_list
$dataSet = new-object "System.Data.DataSet" "ServerListDataSet"
$query = "SET NOCOUNT ON;"
$query = $query + "SELECT server_name "
$query = $query + "FROM   $CMSDatabase.$CMSTable where server_name not in(
    select server_name from $CMSDatabase.dbo.excludeServerList 
  )"

# Create a DataAdapter which you'll use to populate the DataSet with the results
$dataAdapter = new-object "System.Data.SqlClient.SqlDataAdapter" ($query, $CMSServerConnection)
$dataAdapter.Fill($dataSet) | Out-Null

$dataTable = new-object "System.Data.DataTable" "ServerList"
$dataTable = $dataSet.Tables[0]


# for each server
$dataTable | FOREACH-OBJECT {
 Try 
    {   #write-host "server_name: " $_.server_name
        log "server_name : $ServerBConnectionString" yellow
        $ServerBConnectionString = "Data Source="+$_.server_name+";Initial Catalog=$SrcDatabase;User Id=$UserName;PWD=$Password" 
        #write-host "ServerBConnection: " $ServerBConnectionString
        $ServerBConnection = new-object system.data.SqlClient.SqlConnection($ServerBConnectionString);
        $ServerBConnection.Open()

        # create SMO objects so that tables can be created and dropped
        $srv = new-Object Microsoft.SqlServer.Management.Smo.Server($ServerBConnection)
        $db = New-Object Microsoft.SqlServer.Management.Smo.Database
        $db = $srv.Databases.Item($SrcDatabase)

        # collect port number from server
        $cmd3 = new-object System.Data.SQLClient.SQLCommand
        $cmd3.CommandText = "
            SELECT
            @@SERVERNAME as servername,
            cast(CONNECTIONPROPERTY('local_tcp_port') as int) AS port
            INTO $SrcTable
        "
        $cmd3.Connection = $ServerBConnection
        $rowsInserted = $cmd3.ExecuteNonQuery()


        # get port number from table
        $cmd2 = new-object System.Data.SQLClient.SQLCommand
        $cmd2.CommandText = "SELECT port FROM $SrcTable"
        $cmd2.Connection = $ServerBConnection
        $port = [Int32]$cmd2.ExecuteScalar()

        #write-host "port: " $port
        log "port:  $port" yellow

        # update cms table
          $cmd = new-object System.Data.SQLClient.SQLCommand
          $cmd.CommandText = "UPDATE $CMSDatabase.$CMSTable SET server_port = $port WHERE server_name = '"+$_.server_name+"'"
          #write-host "success: " $cmd.CommandText
          $cmd.Connection = $CMSServerConnection
          $rowsUpdated = $cmd.ExecuteNonQuery()

        log "success:  $_.server_name" green        
        #write-host "success: " $_.server_name
        $ServerBConnection.Close()

    } Catch [System.Exception] 
  { 
    $ex = $_.Exception 
    #write-host "failure: " $ex.Message " on server " $_.server_name
    log "failure: $ex.Message on server $_.server_name" red 
    #Write-Host $ex.Message 
  } 
  Finally 
  { 
    #write-host "server_name: " $_.server_name
  } 


}

$CMSServerConnection.Close()
Run Code Online (Sandbox Code Playgroud)

小智 3

您收到的错误实际上已在连接项中注明,但 Microsoft 将其显示为closed won't fix。此连接项中缺少的事实是,子系统SQLPS是通过注册表项设置的。我不知道什么时候、什么真正设定了这个。

该注册表项可在下面的路径中找到,并且在我的本地框中设置为RemoteSigned. 现在我一般不建议更改注册表项,但您可以尝试将其更改为RemoteSigned,您可能会发现您的脚本将运行而不会出现错误。它可能需要重新启动 SQL Agent 服务,不知道。

在此输入图像描述 HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.SqlServer.Management.PowerShell.sqlps120

现在,通过使用,Unrestricted您实际上可以使脚本在执行 PowerShell 脚本时收到提示。这可能是实际生成错误的原因,因为 SQL 代理无法响应提示或不知道如何处理它。确实没有理由使用该策略设置RemoteSigned策略足以允许您在服务器上编写和设置的脚本在没有提示的情况下执行。

除非您深入研究返回的完整错误,否则它可能包含类似于以下消息的文本。这是将执行策略设置为时您可能会收到的提示Unrestricted

安全警告

仅运行您信任的脚本。虽然来自 Internet 的脚本可能很有用,但该脚本可能会损害您的计算机。你想跑吗

[D] 不运行 [R] 运行一次 [S] 暂停 [?] 帮助(默认为“D”):