添加 FileSystem::\\ 到文件路径从 SQL 导出空白 CSV

Adding FileSystem::\\ to file path exports a blank CSV from SQL

首先,这个脚本在我通过 Powershell 运行 时按原样工作。它与所有数据一起到达了目的地。

$adjustedDate = (Get-Date).AddDays(-1)
$dateString = Get-Date -Date $adjustedDate -UFormat "%m"
$dateString += " "
$dateString += Get-Date -Date $adjustedDate -UFormat "%d"
$dateString += " "
$dateString += Get-Date -Date $adjustedDate -UFormat "%y"

$query = "\Drive\Folder\Folder\Folder\Insurance Status.sql"
$instanceName = "SQL-SERVER"

$csvFilePath = "\Drive\Folder\Folder\Folder\Insurance Status "
$csvFilePath += $dateString
$csvFilePath += ".csv"

$results = Invoke-Sqlcmd -InputFile $query -Querytimeout 0 -ServerInstance $instanceName
$results | Export-Csv $csvFilePath -NoTypeInformation

但是,当我 运行 它通过任务计划程序时,任务会成功完成,但导出文件从未到达映射驱动器文件夹中的目标位置。

为了让任务计划程序将 CSV 导出到映射的驱动器,我在我的路径名中添加了 "FileSystem:"。

$adjustedDate = (Get-Date).AddDays(-1)
$dateString = Get-Date -Date $adjustedDate -UFormat "%m"
$dateString += " "
$dateString += Get-Date -Date $adjustedDate -UFormat "%d"
$dateString += " "
$dateString += Get-Date -Date $adjustedDate -UFormat "%y"

$query = "FileSystem::\Drive\Folder\Folder\Folder\Insurance Status.sql"
$instanceName = "SQL-SERVER"

$csvFilePath = "FileSystem::\Drive\Folder\Folder\Folder\Insurance Status "
$csvFilePath += $dateString
$csvFilePath += ".csv"

$results = Invoke-Sqlcmd -InputFile $query -Querytimeout 0 -ServerInstance $instanceName
$results | Export-Csv $csvFilePath -NoTypeInformation

现在,当我 运行 它通过调度程序时,CSV 使其进入映射的驱动器文件夹,但 CSV 是空白的。我错过了什么?

任务计划程序中 运行 脚本的上下文可以 change/have 影响使用 SQL 服务器模块 (sqlps/sqlserver)。您没有指定正在使用的模块,但两个模块中都存在 Invoke-Sqlcmd

在某些情况下,导入 sqlps 模块会将您的路径更改为 SQLSERVER 提供程序 (SQLSERVER:\)。发生这种情况时,它会改变您访问本地文件系统的方式,它不再知道提供程序。这就是为什么它需要添加 FILESYSTEM:: 以便它知道您希望与哪个提供商合作导出数据。

更新版本的 sqlserver 模块更改了提供程序的使用方式,因此它不再将 context/current 目录更改为 SQLSERVER:\ 提供程序。您可能会考虑使用该模块并将其显式导入您的脚本中。

我认为找到了问题的根源。

就您可以采取的措施而言,有多种选择:

  • 加载SQL模块时提示符变为SQLSERVER:\驱动器;因此您可以在脚本的开头加载它,然后更改上下文。例如。以下。这可能就是您在调度程序中看到不同行为的原因;在您的交互式 PS 会话中您已经加载了模块,因此每次您 运行 脚本时它不会重新加载。在任务调度程序中,它每次 运行 都会获得一个新会话,因此每次都必须加载模块。
Import-Module SQLPS
Set-Location c:\ # or wherever; perhaps $PSScriptRoot if you're in a script; though that has no value if you're running code outside of a script file, so may be confusing if you're pasting code in the cmdline
  • 或者您可以使用 pushd/popd 包围对模块的 cmdlet 的调用,以便在调用后重置当前路径
Push-Location c:\ # doesn't matter where, so long as it exists
Invoke-Sqlcmd -InputFile $query -Querytimeout 0 -ServerInstance $instanceName
Pop-Location # moves you back to where you were before you called Push-Location
  • 或者您可以使用不同的方法。我个人非常讨厌使用这个模块的不可预知的副作用,以至于我自己编写了代码:
<#
TOPIC
    about_CCSqlServer

SHORT DESCRIPTION
    Executes SQL code against an MS SQL database.
#>
function Invoke-CCSqlCommand {
    [CmdletBinding(DefaultParameterSetName = 'ByQueryByConnection')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'ByQueryByConnection')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ByPathByConnection')]
        [System.Data.SqlClient.SqlConnection]$Connection
        ,
        [Parameter(Mandatory = $true, ParameterSetName = 'ByQueryByConnectionString')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ByPathByConnectionString')]
        [string]$ConnectionString
        ,
        [Parameter(Mandatory = $true, ParameterSetName = 'ByQueryByProperties')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ByPathByProperties')]
        [string]$DbInstance
        ,
        [Parameter(Mandatory = $false, ParameterSetName = 'ByQueryByProperties')]
        [Parameter(Mandatory = $false, ParameterSetName = 'ByPathByProperties')]
        [string]$DbCatalog = 'master'
        ,
        [Parameter(Mandatory = $true, ParameterSetName = 'ByQueryByConnection')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ByQueryByConnectionString')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ByQueryByProperties')]
        [string]$Query
        ,
        [Parameter(Mandatory = $true, ParameterSetName = 'ByPathByConnection')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ByPathByConnectionString')]
        [Parameter(Mandatory = $true, ParameterSetName = 'ByPathByProperties')]
        [string]$Path
        ,
        [Parameter(Mandatory = $false)]
        [hashtable]$Params = @{}
        ,
        [Parameter(Mandatory = $false)]
        [int]$CommandTimeoutSeconds = 30
        ,
        [Parameter(Mandatory = $false)]
        [int]$ConnectionTimeoutSeconds = 15
        ,
        [Parameter(Mandatory = $false, ParameterSetName = 'ByQueryByProperties')]
        [Parameter(Mandatory = $false, ParameterSetName = 'ByPathByProperties')]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential=[System.Management.Automation.PSCredential]::Empty 
    )
    begin {
        if (! $Connection) {
            if ([System.String]::IsNullOrEmpty($ConnectionString)) {
                $ConnectionString = New-CCSqlConnectionString -DbInstance $DbInstance -DbCatalog $DbCatalog -ConnectionTimeoutSeconds $ConnectionTimeoutSeconds -Credential $Credential -AsString
            }
            $Connection = New-CCSqlConnection -ConnectionString $ConnectionString
        }
    }
    process {
        [hashtable]$splat = @{
            Connection = $Connection
            Params = $Params
            CommandTimeoutSeconds = $CommandTimeoutSeconds
        }
        [System.Data.SqlClient.SqlCommand]$cmd = if ($PSCmdlet.ParameterSetName -like 'ByQuery*') {
                New-CCSqlCommand @splat -Query $Query  
            } else {
                New-CCSqlCommand @splat -Path $Path 
            }
        [System.Data.SqlClient.SqlDataReader]$reader = $cmd.ExecuteReader()
        while (($reader) -and (-not ($reader.IsClosed))) { #improve: consider using as reader rather than data table for better memory consuption with large data sets...
            [System.Data.DataTable]$table = new-object 'System.Data.DataTable'
            $table.Load($reader)
            #write-verbose "TableName: $($table.TableName)" #NB: table names aren't always available
            $table | Select-Object -ExcludeProperty RowError, RowState, Table, ItemArray, HasErrors
        }
    }
    end {
        if ($PSCmdlet.ParameterSetName -notlike '*ByConnection') {
            $connection.Close() #only close the connection if we own it; if it was passed to us, leave it open
        }
    }
}

function New-CCSqlCommand {
    [CmdletBinding(DefaultParameterSetName = 'ByQuery')]
    [OutputType('System.Data.SqlClient.SqlCommand')]
    param (
        [Parameter(Mandatory = $true)]
        [System.Data.SqlClient.SqlConnection]$Connection # Since the standard use case for this will be within Invoke-CCSqlQuery, and that works best by reusing an existing connection, we're best off only providing the conneciton option (at least for now)
        ,
        [Parameter(Mandatory = $true, ParameterSetName = 'ByQuery')]
        [string]$Query
        ,
        [Parameter(Mandatory = $true, ParameterSetName = 'ByPath')]
        [string]$Path
        ,
        [Parameter(Mandatory = $false)]
        [hashtable]$Params = @{}
        ,
        [Parameter(Mandatory = $false)]
        [int]$CommandTimeoutSeconds = 30 #30 is the default 
    )       
    $cmd = new-object -TypeName 'System.Data.SqlClient.SqlCommand'
    $cmd.Connection = $connection
    if ($PSBoundParameters.ContainsKey('CommandTimeoutSeconds')) {
        $cmd.CommandTimeout = $CommandTimeoutSeconds #https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.commandtimeout(v=vs.110).aspx
    }
    #load in our query
    switch ($PSCmdlet.ParameterSetName) {
        'ByQuery' {$cmd.CommandText = $Query; break;}
        'ByPath' {$cmd.CommandText = Get-Content -Path $Path -Raw; break;}
        default {throw "ParameterSet $($PSCmdlet.ParameterSetName) not recognised by Invoke-SQLQuery"}
    }
    #assign parameters as required 
    #NB: these don't need declare statements in our query; so a query of 'select @demo myDemo' would be sufficient for us to pass in a parameter with name @demo and have it used
    #we can also pass in parameters that don't exist; they're simply ignored (sometimes useful if writing generic code that has optional params)
    $Params.Keys | ForEach-Object{$cmd.Parameters.AddWithValue("@$_", $Params[$_]) | out-null}
    $cmd
}
function New-CCSqlConnection {
    [OutputType('System.Data.SqlClient.SqlConnection')]
    [CmdletBinding(DefaultParameterSetName = 'ByConnectionString')]
    param (
        [Parameter(ParameterSetName = 'ByConnectionString', Mandatory = $true)]
        [string]$ConnectionString
        ,
        [Parameter(ParameterSetName = 'ByProperties', Mandatory = $true)]
        [string]$DbInstance
        ,
        [Parameter(ParameterSetName = 'ByProperties', Mandatory = $false)]
        [string]$DbCatalog = 'master'
        ,
        [Parameter(ParameterSetName = 'ByProperties', Mandatory = $false)]
        [int]$ConnectionTimeoutSeconds = 15  #15 is the SQL default: https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.connectiontimeout(v=vs.110).aspx
        ,
        [Parameter(ParameterSetName = 'ByProperties', Mandatory = $false)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty 
        ,
        [Parameter(Mandatory = $false)]
        [Switch]$SuppressAutoOpen
    )
    if ($PSCmdlet.ParameterSetName -eq 'ByProperties') {
        $ConnectionString = New-CCSqlConnectionString -DbInstance $DbInstance -DbCatalog $DbCatalog -ConnectionTimeoutSeconds $ConnectionTimeoutSeconds -Credential $Credential -AsString
    }
    $connection = New-Object 'System.Data.SqlClient.SqlConnection'
    $connection.ConnectionString = $connectionString
    if (! $SuppressAutoOpen) {
        $connection.Open()    
    }
    $connection
}
function New-CCSqlConnectionString {
    param (
        [Parameter(Mandatory = $true)]
        [string]$DbInstance
        ,
        [Parameter(Mandatory = $false)]
        [string]$DbCatalog = 'master'
        ,
        [Parameter(Mandatory = $false)]
        [int]$ConnectionTimeoutSeconds = 15  #15 is the SQL default: https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.connectiontimeout(v=vs.110).aspx
        ,
        [Parameter(Mandatory = $false)]
        [System.Management.Automation.Credential()]
        [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty 
        ,
        [Parameter(Mandatory = $false)]
        [Switch]$AsString
    )
    #Useful document on possible properties: https://www.connectionstrings.com/all-sql-server-connection-string-keywords/
    $connectionString = ('Server={0};Database={1}' -f $DbInstance,$DbCatalog)
    if ($Credential -eq [System.Management.Automation.PSCredential]::Empty) {
        $connectionString = ('{0};Integrated Security=True' -f $connectionString)
    } else {
        $connectionString = ('{0};User Id={1};Password={2}' -f $connectionString, $Credential.UserName, $Credential.GetNetworkCredential().Password)    
    }
    if ($PSBoundParameters.ContainsKey('ConnectionTimeoutSeconds')) {
        $connectionString = '{0};Connection Timeout={1}' -f $connectionString, $ConnectionTimeoutSeconds
    }
    if ($AsString) {
        $connectionString
    } else {
        [pscustomobject]@{ConnectionString=$connectionString}
    }
}

Invoke-CCSqlCommand -DbInstance $instanceName -Path $query -CommandTimeoutSeconds 0
Invoke-Sqlcmd -InputFile $query -Querytimeout 0 -ServerInstance $instanceName

感谢所有反馈。我想通了...

这最终是解决问题的代码:

$adjustedDate = (Get-Date).AddDays(-1)
$dateString = Get-Date -Date $adjustedDate -UFormat "%m"
$dateString += " "
$dateString += Get-Date -Date $adjustedDate -UFormat "%d"
$dateString += " "
$dateString += Get-Date -Date $adjustedDate -UFormat "%y"

$query = "\Drive\Folder\Folder\Folder\Insurance Status.sql"
$instanceName = "SQL-SERVER"

$csvFilePath = "FileSystem::\Drive\Folder\Folder\Folder\Insurance Status "
$csvFilePath += $dateString
$csvFilePath += ".csv"

$results = Invoke-Sqlcmd -InputFile $query -Querytimeout 0 -ServerInstance $instanceName
$results | Export-Csv $csvFilePath -NoTypeInformation

我真正的问题非常明显。当我第一次 运行 遇到任务调度程序 运行 脚本的问题时,我将我的调度程序凭据更改为 "admin" 集,我最终意识到它没有正确的凭据来访问 SQL 服务器。在更改代码以包含完整的 "FileSystem::" 路径并将凭据切换回我的标准用户凭据后,一切正常。感谢您的帮助!