This question already has answers here:
Powershell Try Catch invoke-sqlcmd
(3 answers)
Error detection from Powershell Invoke-Sqlcmd not always working?
(3 answers)
Closed 4 years ago.
I have a function that I pass a server name to, then select some data from that server and insert it into a table on a centralized server. The list of servers is generated from a centralized server that isn't always up to date, and I would like for it to keep processing and log any errors that occur. However, my try-catch block doesn't seem to capture any errors.
I saw this question and tried that, and I still can't capture the errors. Here's a generic version of my function:
function Get-QueryData
{
[CmdletBinding()]
param
(
[string] $server
)
$QuerySelect = "select * from dbo.Table"
try
{
$results = Invoke-Sqlcmd -ServerInstance $server `
-Database TheDatabase `
-Query $QuerySelect `
-QueryTimeout 20 `
-OutputSqlErrors $True `
-ConnectionTimeout 5 `
-ErrorAction Stop
}
catch
{
#to-do: log error
Write Host " An error occurred trying to get query info"
}
# remaining function inserts into central server, this part doesn't fail
# unless the first part fails
}
The error I get:
Invoke-Sqlcmd: A network-related or instance-specific error occurred
while establishing a connection to SQL Server... CategoryInfo:
InvalidOperations (:) [Invoke-SqlCmd] SqlException
FullQualifiedErrorId :
SqlExecutionError,Microsoft.SqlServer.Management.Powershell.GetScriptCommand
The reason I'm getting this error is that the server was stood up, and then had to be rebuilt, but it was already added to our central management server. I can't rely on this being cleaned up right away so I need the powershell to log the error and keep moving.
Output from $PsVersionTable.PSVersion
Major - 5
Minor - 1
Build - 14409
Revision - 1012
In the Try{} set $flag=$True
In the Try{} remove the $results variable from the get-sqlcmd
In the Catch{} set $flag=$false
Add a finally{} that checks for the flag
In the Finally{} add an if(!x){y}else{z}
In the Finally{if(!x){y}else{z}}
if $flag is $false write to log and exit
if $flag is $true do your work and then write to log
I've provided [sting]$message >> $targetFile for simple logging. There are way better ways to do logging and I've used this example only as an simple example and not a suggestion.
function Get-QueryData
{
[CmdletBinding()]
param(
[string] $server
)
$QuerySelect = "select * from dbo.Table"
try
{
$flag = $true
Invoke-Sqlcmd -ServerInstance $server `
-Database TheDatabase `
-Query $QuerySelect `
-QueryTimeout 20 `
-OutputSqlErrors $True `
-ConnectionTimeout 5 `
-ErrorAction Stop
}
catch
{
$flag = $false
#to-do: log error with $_ which contains the error object
Write-Host "An error occurred trying to get query info"
$errorMessage = $_.Exception.Message
}
finaly
{
# log errors if flag false
if (!$flag){
"[$(get-date)] - $errorMessage" >> `
$(some target accessible path to a writable file)
#Stop the function and return an exit code of 1
Return 1
} else {
# remaining function inserts into central server
"[$(get-date)] - Short Meaningful Success Message" >> `
$(some target accessible path to a writable file)
}
}
}
So I'm an idiot and I was copying from an outdated copy. My NEW version that reflects the changes made per the link in my question works perfectly.
Related
I need to use ApplicationIntent=ReadOnly with my SQL command in powershell which is connecting to a replica database. Can anyone help ?
Since replicas servers could not be accessed directly. So I need to use this command. I know how to manually do it but need help on code.
$SQLQuery = "SELECT x.SCode, x.DatabaseName FROM dbo.Logins x ORDER BY x.SCode"
$auth = #{Username = $SQLUserName; Password = $SQLAdminPassword}
try
{
$allTenants = Invoke-Sqlcmd -Query $SQLQuery -ServerInstance $SQLServerName -Database 'SShared'-QueryTimeout -0 #Auth -ErrorAction Stop
Write-Log -LogFileName $logfile -LogEntry ("Found {0} tenants" -f $allTenants.Count)
}
I am geeting the below error using this -
Exception Message A network-related or instance-specific error
occurred while establishing a connection to SQL Server
The server was not found or was not accessible. Verify that the
instance name is correct and that SQL Server is configured to allow
remote connections. (provider: Named Pipes Provider, error: 40
- Could not open a connection to SQL Server)
There's a few ways that you can do this.
Easy way
dbatools
There is a PowerShell module for interacting with SQL Server created by the SQL Server community called dbatools.
In the module, there is a function called Invoke-DbaQuery which is essentially a wrapper for Invoke-Sqlcmd.
This function has a parameter, -ReadOnly, that you can use that was created exactly for this scenario.
# Changing your $auth to a PSCredential object.
$cred = [System.Management.Automation.PSCredential]::New(
$SqlUserName,
(ConvertTo-SecureString -String $SqlAdminPassword -AsPlainText -Force))
# Splatting the parameters for read-ability.
$QueryParams = #{
Query = $SQLQuery
SqlInstance = $SQLServerName
Database = 'SShared'
QueryTimeout = 0
SqlCredential = $cred
ReadOnly = $true # <-- Specifying read-only intent.
ErrorAction = 'Stop'
}
$allTenants = Invoke-DbaQuery #QueryParams
Other way
Invoke-Sqlcmd
If you can't, won't, don't want to use dbatools, you can still use Invoke-Sqlcmd. The latest release at the time of writing, has the option to specify the parameter -ConnectionString.
You can state that it's read-only there.
# Splatting again for read-ability.
$SqlcmdParams = #{
Query = $SQLQuery
QueryTimeout = 0
ConnectionString = "Data Source=$SQLServerName;Initial Catalog=SShared;User ID=$SqlUserName;Password=$SqlAdminPassword;Integrated Security=false;ApplicationIntent=ReadOnly" # <-- Specifying read-only intent.
ErrorAction = 'Stop'
}
Invoke-Sqlcmd #SqlcmdParams
I have setup a process for Restoring Databases using the Restore-SqlDatabase cmdlet and scheduled this into a SQl Agent job. It's been working fine until I tried to restore a 160GB database. It basically fails after 600s with the error "The wait operation timed out". I've tried adding in a StatementTimeout of 0 to see if it would get round this and also change the Remote query Timeout on the SQL Server but it still bobmbs out after 10 minutes. Does anyone know if there is another setting I can change to increase the timeout? The Code I'm running is:
# Load Module path! its not loading by default in calls from powershell
$env:PSModulePath=$env:PSModulePath + ";" + "C:\Program Files\Microsoft SQL Server\110\Tools\PowerShell\Modules\SQLPS\"
## Load module and run functions now
#Import SQL Server PowerShell Provider.
Import-Module "sqlps" -DisableNameChecking
#GEt Around Wait Timeout Error with Remote Connection via PoSh
$server = "THESERVER"
$serverConn = new-object ("Microsoft.SqlServer.Management.Smo.Server") $server
$serverConn.ConnectionContext.StatementTimeout = 0
#Restore Latest Copy of Test Database that was copied over
Restore-SqlDatabase -ServerInstance $server -Database "Test" -BackupFile "H:\SQLBACKUP\DATABASE_Refresh\Test_Latest.BAK" -ReplaceDatabase
Error That Appears is:
Restore-SqlDatabase : The wait operation timed out
At H:\SQLBACKUP\_AutoScripts\5_Test_DBRESTORE.ps1:16 char:1
+ Restore-SqlDatabase -ServerInstance $server -Database "Test ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Restore-SqlDatabase], Win32Exception
+ FullyQualifiedErrorId : ExecutionFailed,Microsoft.SqlServer.Management.PowerShell.RestoreSqlDatabaseCommand
It looks like Restore-SqlDatabase has a -ConnectionTimeout parameter:
Specifies the number of seconds to wait for a server connection before a timeout failure. The timeout value must be an integer between 0 and 65534. If 0 is specified, connection attempts do not timeout.
So your command can become:
Restore-SqlDatabase `
-ServerInstance $server `
-Database "Test" `
-BackupFile "H:\SQLBACKUP\DATABASE_Refresh\Test_Latest.BAK" `
-ReplaceDatabase `
-ConnectionTimeout 0
I have a Powershell script that invokes a saved SQL Query file and runs it on a specific Server & Database. That part is working well, the issue is that I would like to save the SQL Messages that it generates to a log file (see picture).
SQL Output from after Query is run
This is not working with my current code, and I believe that's because since it's technically not Query output but instead reindexing and updating tables, not fetching data.
My current relevant code is:
{
Import-Module SQLPS
$Data = Invoke-Sqlcmd -InputFile $SQLQuery -ServerInstance $Server -Database $Database -QueryTimeout 0
$Data | out-file "$Output$Database$date.txt"
}
But that just generates an empty text file. I'm looking to get the info on rebuilding indexes and the updates it's doing saved off into a different file through Powershell. You can do this through SSMS by right clicking in the Messages window and clicking "Save Results As..." but looking to include this in my automation since it's running as a Scheduled Task and no one is on SSMS.
Powershell v3/Windows Server 2012/SQL SSMS 2014
Any help would be appreciated!! This is my first post so sorry for odd formatting.
It looks like the following link explains exactly this problem:
https://sqlnotesfromtheunderground.wordpress.com/2015/09/09/powershell-outputting-a-sql-server-query-result-from-the-message-tab/
Essentially, what you are seeing in the 'Messages' tab are not results from the query, but rather just PRINT statements (essentially the same as Write-Host or Console.WriteLine). If you use Invoke-SqlCommand2, its -Verbose option will capture these PRINT statements to the Verbose PowerShell stream. To then write this stream to a text file, you have to specify the specific stream (in this case, 4):
Invoke-Sqlcmd2 -ServerInstance . -Database master -Query "PRINT 'Export This result'" -Verbose 4> Out-File C:\Temp\Test.txt
I had the same issue but instead in powershell script i use it in a command and i used -verbose.
like this
Invoke-Sqlcmd -ServerInstance '.\Your_server_instance' -Database 'DATABASE_Name' -InputFile "D:\Your_script.sql" verbose 4> "C:\sql\YOUR_OUTPUT_FILE.txt"
so i think this code should work for you
{
Import-Module SQLPS
$Data = Invoke-Sqlcmd -InputFile $SQLQuery -ServerInstance $Server -Database $Database -QueryTimeout 0
$Data -verbose *> "$Output$Database$date.txt"
}
for -verbose *> it streams All output you can redirect specific streams :
1 Success output
2 Errors
3 Warning messages
4 Verbose output
5 Debug messages
I'm working on a project to create some Excel reports with data from MSSQL databases and need some help with the end result.
Disclaimer - I'm not great with PowerShell and MSSQL).
So far, with the help of the internet, I've managed to create a .ps1 file that elevates itself, imports a PS module called PSExcel (https://github.com/RamblingCookieMonster/PSExcel), imports the SQLPS module and runs three separate queries to export to three separate .xlsx files.
My script for the last few parts is:
# Import PSExcel module
Import-Module C:\scripts-and-reports\Modules\PSExcel
# Import SQLPS module
Import-Module sqlps
# Import PSExcel module
Import-Module C:\scripts-and-reports\Modules\PSExcel
# Import SQLPS module
Import-Module sqlps
# Create individuals
invoke-sqlcmd -inputfile "C:\scripts-and-reports\individuals.sql" -serverinstance "SERVER\INSTANCE" -database "DATABASE" |
Export-XLSX -WorksheetName "Individuals" -Path "C:\scripts-and-reports\Individuals.xlsx" -Table -Force
# Create joint parties
invoke-sqlcmd -inputfile "C:\scripts-and-reports\joint-parties.sql" -serverinstance "SERVER\INSTANCE" -database "DATABASE" |
Export-XLSX -WorksheetName "Joint Parties" -Path "C:\scripts-and-reports\Joint Parties.xlsx" -Table -Force
# Create organisations
invoke-sqlcmd -inputfile "C:\scripts-and-reports\organisations.sql" -serverinstance "SERVER\INSTANCE" -database "DATABASE" |
Export-XLSX -WorksheetName "Organisations" -Path "C:\scripts-and-reports\Organisations.xlsx" -Table -Force
I've tried to no avail to combine the last two query exports into the first query's export as additional worksheets so that I only have a single Excel workbook to hand to my boss, but I think I must be approaching it incorrectly.
When I read the example on Line 94 of ../Export-XLSX.ps1 and try to implement it in my scenario by changing the file names to match each other, the last query replaces the first in the outputted .xlsx file. This must be because of the -Force. Changing that to -Append won't help because then it says the file already exists.
Can anyone help me by first showing me where I'm going wrong and then pointing me in the right direction (this may end up in a walkthrough).
Please and thanks!
---** UPDATE **---
With #gms0ulman's fix to Export-XLSX.ps1, it looks like it's going to work as I've tested it with different SQL queries to what I need and it adds the worksheets OK.
The queries I'm using for the test are
SELECT DISTINCT
case mt.[Status]
when 0 then 'In Progress'
When 1 then 'On Hold'
when 2 then 'Completed'
when 3 then 'Not Proceeding'
else 'Unknown' end as MatterStatus,
mt.LastUpdatedOn as LastModified
from matter mt
where mt.LastUpdatedOn >= '2016-07-01'
AND (mt.[status] = 0 or mt.[status] = 1)
While this (and the other two iterations of it in my PS script) works, my actual queries don't. The queries themselves work, and the first export too, but when -Append is used in PS with the invoke-sqlcmd and invoke-sqlcmd -inputfile "query2.sql" -serverinstance "" -database "" | Export-XLSX -WorksheetName "Joint Parties" -Path "C:\scripts-and-reports\Matter Details.xlsx" -Table -Append, the two appended worksheets get the error:
Exceptions calling "SaveAs" with "1" argument(s): "Error saving file C:\scripts-and-reports\Matter Details.xlsx"
At C:\scripts-and-reports\Modules\PSExcel\Export-XLSX.ps1:496 char:13
$Excel.SaveAs($Path)
~~~~~~~~~~~~~~~~~~~~
CategoryInfo : NotSpecified: (:) [], MethodInvocationException
FullyQualifiedErrorId : InvalidOperationException
The problem is with the module you're using. -Append should work. In the Export-XLSX.ps1 file, look at lines 351 and 371.
As you are providing an existing path, 351 evaluates to true and 371 never gets executed.
Line 371 is where the module creates a new worksheet for you.
if (($Append -or $ClearSheet) -and ($PSBoundParameters.ContainsKey('Excel') -or (Test-Path $Path)) ) # line 351
{
$WorkSheet=$Excel.Workbook.Worksheets | Where-Object {$_.Name -like $WorkSheetName}
if($ClearSheet)
{
$WorkSheet.Cells[$WorkSheet.Dimension.Start.Row, $WorkSheet.Dimension.Start.Column, $WorkSheet.Dimension.End.Row, $WorkSheet.Dimension.End.Column].Clear()
}
if($Append)
{
$RealHeaderCount = $WorkSheet.Dimension.Columns
if($Header.count -ne $RealHeaderCount)
{
$Excel.Dispose()
Throw "Found $RealHeaderCount existing headers, provided data has $($Header.count)."
}
$RowIndex = 1 + $Worksheet.Dimension.Rows
}
}
else
{
$WorkSheet = $Workbook.Worksheets.Add($WorkSheetName) #line 371
}
To get around this, I've added a couple of lines to the Export-XLSX.ps1 file.
Now, even if the path exists, it will:
check if a $WorksheetName is provided
check this worksheet does not already exist
create new worksheet if both are true.
Note you will have to Remove-Module and Import-Module for changes to be recognised. You will need to use -Append. I used -Force on first worksheet, not the second one. Also, you may find -Verbose helpful as it provides you with more information as the script runs - good for debugging.
if (($Append -or $ClearSheet) -and ($PSBoundParameters.ContainsKey('Excel') -or (Test-Path $Path)) )
{
# New line: even if path exists, check for $WorkSheetName and that this worksheet doesn't exist
if($WorkSheetName -and $Excel.Workbook.Worksheets | Where-Object {$_.Name -like $WorkSheetName} -eq $null)
{
# if you have $WorksheetName and it doesn't exist, create worksheet.
$WorkSheet = $Workbook.Worksheets.Add($WorkSheetName)
}
else
{
$WorkSheet=$Excel.Workbook.Worksheets | Where-Object {$_.Name -like $WorkSheetName}
if($ClearSheet)
{
$WorkSheet.Cells[$WorkSheet.Dimension.Start.Row, $WorkSheet.Dimension.Start.Column, $WorkSheet.Dimension.End.Row, $WorkSheet.Dimension.End.Column].Clear()
}
if($Append)
{
$RealHeaderCount = $WorkSheet.Dimension.Columns
if($Header.count -ne $RealHeaderCount)
{
$Excel.Dispose()
Throw "Found $RealHeaderCount existing headers, provided data has $($Header.count)."
}
$RowIndex = 1 + $Worksheet.Dimension.Rows
}
} # this is also new.
}
else
{
$WorkSheet = $Workbook.Worksheets.Add($WorkSheetName)
}
I have a script that I created a powershell script based on a script I found, Start-Migration. It is a great script and gave me a lot of really good ideas; however I am running into some issues when attempting to restore large databases or databases that take longer than 10 minutes. I have attempted to use both them invoke-sqlcmd2 function I found and the class for the restore for the microsoft.sqlserver.management.smo namespace. both of which are timing out after 10 minutes. I have also tried increasing the connection timeout even setting the connection to 1200. any suggestions would be welcomed.
Function Restore-SQLDatabase {
<#
.SYNOPSIS
Restores .bak file to SQL database. Creates db if it doesn't exist. $filestructure is
a custom object that contains logical and physical file locations.
.EXAMPLE
$filestructure = Get-SQLFileStructures $sourceserver $destserver $ReuseFolderstructure
Restore-SQLDatabase $destserver $dbname $backupfile $filestructure
.OUTPUTS
$true if success
$true if failure
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[object]$server,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$dbname,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$backupfile,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[object]$filestructure
)
$servername = $server.Name
$server.ConnectionContext.StatementTimeout = 0
$restore = New-Object "Microsoft.SqlServer.Management.Smo.Restore"
foreach ($file in $filestructure.databases[$dbname].destination.values) {
$movefile = New-Object "Microsoft.SqlServer.Management.Smo.RelocateFile"
$movefile.LogicalFileName = $file.logical
$movefile.PhysicalFileName = $file.physical
$null = $restore.RelocateFiles.Add($movefile)
}
Write-Host "Restoring $dbname to $servername" -ForegroundColor Yellow
try{
$Percent = [Microsoft.SqlServer.Management.Smo.PercentCompleteEventHandler]{
Write-Progress -Id 1 -Activity "Restoring $dbname to $ServerName" -PercentComplete $_.Percent -Status ([System.String]::Format("Progress: {0}%",$_.Percent))
}
$restore.add_PercentComplete($Percent)
$restore.PercentCompleteNotification = 1
$restore.add_Complete($Complete)
$restore.ReplaceDatabase = $true
$restore.Database = $dbname
$restore.Action = "Database"
$restore.NoRecovery = $false
$device = New-Object -TypeName Microsoft.SqlServer.Management.Smo.BackupDeviceItem
$device.Name = $backupfile
$device.DeviceType = "File"
$restore.Devices.Add($device)
Write-Progress -Id 1 -Activity "Restoring $dbname to $servername" -PercentComplete 0 -Status([System.String]::Format("Progress: {0}%",0))
$restore.SqlRestore($servername)
# $query = $restore.Script($ServerName)
# Write-Host $query
# Invoke-Sqlcmd2 -ServerInstance $servername -Database master -Query $query -ConnectionTimeout 1200
# Write-Host "Restoring $dbname to $servername from " $restore.Devices.ToString() -ForegroundColor Magenta
Write-Progress -Id 1 -Activity "Restore $dbname to $servername" -Status "Complete" -Completed
return $true
}
catch{
Write-Error $_.Exception.ToString()
Write-Warning "Restore failed: $($_.Exception.InnerException.Message)"
return $false
}
when the restore process takes place ,$restore.SqlRestore($ServerName), on my larger databases it returns saying that the script timed out. I am trying to figure out how to correct this. I have tried increasing the statementtimeout = 1200 and it still stops after 10 minutes. i even attempted to us an invoke-sqlcmd As you can see I commented it out when trying different options. I am at wits end right now.
If you have to use the script you found
I think that this line `$server.ConnectionContext.StatementTimeout = 0' is not actually doing anything or changing the timeout threshold.
Try changing the line to this, instead
$server = New-Object("Microsoft.SqlServer.Management.Smo.Server") $server
$server.ConnectionContext.StatementTimeout = 0
$server.ConnectionContext.Connect()
However, I would recommend avoiding this approach entirely, because you can use the much much easier SQLPS Cmdlets. The approach you're using comes from a long time ago, in which we didn't have actual PowerShell cmdlets to work with SQL.
Nowadays, we have the Restore-SQLDatabase cmdlet, which lets you do with one line what your code now is doing in ~30 lines!!
How to Use Restore-SQLDatabase instead
This is so much easier, and you'll really thank me, I think.
Restore-SqlDatabase -ServerInstance "$server\InstanceName" `
-Database $dbname `-BackupFile $BackupFile
and...that's it. One single line! And you can also specify the timeout easily here, unlike before. To specify the maximum timeout:
Restore-SqlDatabase -ServerInstance "$server\InstanceName" `
-Database $dbname -BackupFile $BackupFile -ConnectionTimeout 0
That should get you started. Trust me, using the Cmdlets is so much easier than using some random script you find online.
Okay I got it to work. I am not completely sure why it worked but here is what I did. I created a parameter $Server as follows:
$Server = New-Object Microsoft.SqlServer.Management.Smo.Server $Source
$Server.ConnectionContext.ConnectTimeout = 0
Now when I call the function that actually does the restore, I was doing something that I didn't need to do:
$ServerName = $server.Name
instead of using the $ServerName, I used the object $server:
$restore.SqlRestore($servername) # Original Script
$restore.SqlRestore($server) # Change
and now it stays open long enough to complete the restore process.