Powershell Invoke-sqlcmd keeping connection open and trying to reuse it - powershell

I have a Powershell script that uses invoke-sqlcmd to apply scripts to a series of development databases. I loop through a list of scripts and compare it to the current release level of the database and then apply the required scripts to get the DB to the release level it needs to be at. Certain databases are reference databases and are in a READ_ONLY state. I connect to those database run an alter DB script setting them to READ_WRITE apply the script then change the back to READ_ONLY. Overall the script works well, the issue is it looks like when PowerShell first opens a connection to the database and applies the first script and then goes to alter the DB back to READ_ONLY the database has objects locked. I've traced it back to the previous connection and a Shared_Transaction_Workspace lock (sys.dm_tran_locks) for what looks to be the previous powershell connection. Why is this connection still open after the invoke-sqlcmd has completed and is there anything I can do about it? Can I force invoke-sqlcmd to use a new connection for each invocation of the cmdlet?
I have tried a messy fix killing the offending connection and then retrying the connection but I think there is something better.

I've always done this and it seems to work:
[System.Data.SqlClient.SqlConnection]::ClearAllPools()

Well, I know that this is a very old post and the people from Microsoft told that fixed this issue (as told the article mentioned by David Brabant) but maybe I'm not the luckiest guy and have to make an workaround to make it happens.
Even running Microsoft SQL Server 2012 (SP1) - 11.0.3128.0 (X64) I had the same issue and after make some researches I got a way to get some parameter from Invoke-Sqlcmd as output so I can get the Session ID of the current user process with the built-in ##SPID global variable from the SQL Server and make a connection with ADO.NET to execute a KILL clause to close the opened connection.
So let's to the workaround applied in my case
#Invoke the Invoke-Sqlcmd to execute an script from a file
Invoke-Sqlcmd -Server "[SERVER_NAME]" -Database [DATABASE_NAME] -Username [USER] -Password [PASSWORD] -InputFile [DOT_SQL_FILE_PATH]
#Invoke the Invoke-Sqlcmd to execute a inline SQL statement to get the SessionID as a Powershell variable
$SQLSession = Invoke-Sqlcmd -Server "[SERVER_NAME]" -Database [DATABASE_NAME] -Username [USER] -Password [PASSWORD] -query "select ##spid as SessionID"
# Build query to execute KILL clause
$DbQuery = "KILL " + $SQLSession.SessionID;
# Create SQL connection with ADO.NET
$DbConnection = New-Object System.Data.SqlClient.SqlConnection
$DbConnectionString = "Server = [SERVER_NAME]; Database = [DATABASE_NAME]; User ID=[USER]; Password=[PASSWORD];"
$DbConnection.ConnectionString = $DbConnectionString
$DbConnection.Open()
# Create SQL command for KILL clause
$DbCommand = New-Object System.Data.SQLClient.SQLCommand
$DbCommand.Connection = $DbConnection
$DbCommand.CommandText = $DbQuery
# Execute KILL clause
$DbCommand.ExecuteNonQuery()
# Close connection
$DbConnection.Close()
I hope that it helps

Even though I am using the newest version of SSMS (Version 16.5.3 - Build 13.0.16106.4), I still get this issue. I haven't figured out what the "right" way of forcing the connection closed is, but I have a work-around that is simple and resolves the issue for me. If you just need to get the connection off the database, you can do the following:
Run normal command(s)
Invoke-Sqlcmd -ServerInstance "SOME_SERVER" -Database "SOME_DB" ...
When you are ready to eliminate the connection from the database:
Invoke-Sqlcmd -ServerInstance "SOME_SERVER" -Database "SOME_DB" -Query "use [master];"
This will switch the connection to master, thus removing it from the database of interest. If you absolutely need the connection closed, I think you need to resort to SqlClient or such.

Related

How To Prevent Invoke-SQLCMD from trimming Leading Zero's

In powershell I have a script and every time it runs it needs to increment a number in a DB by one. Once it reaches 9999 it resets back to 0001.
I have this functionality worked out in powershell my issue is that Invoke-SQLCMD
keeps stripping out any leading 0's
so If I want to update the DB value to 0001 it only updates it to 1
Is there any way to have SQLCMD keep leading 0's?
Invoke-Sqlcmd -ServerInstance $dataSource -Database $database -Username $user -password $pass -Query "UPDATE DBO.TABLE_NAME SET sequence_no = $newFileNum"
invoke SQLCMD does this by default.
One way is when you run your query against SQL to get the value in there you can use .ToString("0000") to put the 0's back in or your can use this as your SQL query.

Credentials using Invoke-Sqlcmd against sql azure

I'm attempting to run a powershell build script against a sql azure database but receiving Login failed for user 'X'.
I'm fairly convinced the credentials are correct as they were taken straight from the live application config.
This is the command I'm using:
Invoke-Sqlcmd -InputFile "Build.sql" -ServerInstance $server -Database $database `
-WarningAction SilentlyContinue -OutputSqlErrors $false `
-Username $username -Password $password -EncryptConnection
I had this working with sqlcmd in a batch file so I'm wondering if it's got anything to do with the way the credentials are being sent, trusted_connection=false doesn't appear to be an option I can try.
It could be the password contains a few special characters that Azure/Invoke-sqlcmd does not handle (such as dollar, single or double quote, parentheses). I tried using the Azure interface and surrounding the password with single-quotes (we had a dollar-sign in the password), but that did not work. So, we simply removed the special character and now it is OK. see: Powershell Invoke-Sqlcmd Login Failed
and
https://mohitgoyal.co/2017/08/09/vsts-azure-sql-database-deployment-task-keeps-failing-with-error-login-failed-for-user/
When connecting to SQL Azure the login name must be of the form user#server. So if you created an user 'foo' and a server 'bar', the login must be foo#bar. See Managing Databases and Logins in Azure SQL Database:
Because some tools implement tabular data stream (TDS) differently, you may need to append the Azure SQL Database server name to the login in the connection string using the <login>#<server> notation. In these cases, separate the login and Azure SQL Database server name with the # symbol. For example, if your login was named login1 and the fully qualified name of your Azure SQL Database server is servername.database.windows.net, the username parameter of your connection string should be: login1#servername.
CREATE LOGIN also explains this:
In some methods of connecting to SQL Database, such as sqlcmd, you must append the SQL Database server name to the login name in the connection string by using the <login>#<server> notation. For example, if your login is login1 and the fully qualified name of the SQL Database server is servername.database.windows.net, the username parameter of the connection string should be login1#servername.

Powershell connect to firebird

Hey I was wondering how one could connect to a firebird database (gdb) file from within powershell. Is there a way to use the .net data provider for firebird to connect? Is there a way to connect with System.Data.Odbc.OdbcConnection to firebird?
function Get-ODBC-Data{
param([string]$query=$(throw 'query is required.'))
$conn=New-Object System.Data.Odbc.OdbcConnection
$connStr = "Driver={Red Database/Firebird driver};Server=localhost;Port=****;Database=*.fdb;Uid=user;Pwd=userpassword;"
$conn.ConnectionString= $connStr
$conn.open
$cmd=new-object System.Data.Odbc.OdbcCommand($query,$conn)
$cmd.CommandTimeout=15
$ds=New-Object system.Data.DataSet
$da=New-Object system.Data.odbc.odbcDataAdapter($cmd)
[void]$da.fill($ds)
$ds.Tables[0]
#Write-Output $ds.Tables[0].rows.count
$conn.close()
}
$query = #"
select count(*)
from UNIFO_PAYMENT U
join DOCUMENT D on D.ID = U.ID
"#
$result = Get-ODBC-Data -query $query
The code from #Alexandr is from https://www.andersrodland.com/working-with-odbc-connections-in-powershell/
I suggest you read the entire thing. While he isn't exactly explaining the code either, I think it's fairly self-documenting, the only information that is missing, is how to get/use the Firebird driver.
You can get the ODBC driver installer from firebird https://www.firebirdsql.org/en/odbc-driver/
After Installing it, open windows ODBC administration (odbcad32.exe), check the drivers tab to make sure that the "Firebird/Interbase(r) driver" is there.
From here you can either use that driver name to run #Alexandr 's code
$connStr = "Driver=Firebird/Interbase(r) driver;Server=localhost;Port=****;Database=*.fdb;Uid=user;Pwd=userpassword;"
OR Go to either the User DSN tab, or the system DSN tab. Click add, select the firebird driver, set up the DSN with a name, the path to your database and the other required database registration info you normally would. Keep the DSN name simple, it's an identifier you will use.
Then you can simply replace the $constr with
$connStr = "DSN=YourDsnName;"
Yes, powershell allows you to create .Net classes and call .Net methods. See here. So with little trouble you can convert your C# code to powershell.

Run SQL script file from powershell

I am trying to run queries stored in a text file from PowerShell. I use following to do that;
Invoke-Expression 'sqlcmd -d TestDB -U $user -P $pw -i "E:\SQLQuery1.sql"'
If an error or exception occurs when executing the queries from the .sql file, how can I capture that in my Powershell script? How can I get the script output?
NOTE: I cannot use invoke-sqlcmd
To answer the question
If some error or exception occurred when executing .sql file how can I get that into my PowerShell script? How can I get the script output?"
Invoke-Expression returns the output of the expression executed. However, it may only capture STDOUT, not STDERR (I haven't tested, as I don't use this method), so you might not get the actual error message.
From the Help:
The Invoke-Expression cmdlet evaluates or runs a specified string as a command and returns the results of the expression or command
The better route is to use the PowerShell method you already have available - Invoke-SQLCmd is installed if you have installed any of the SQL Server 2008 (or newer) components/tools (like SSMS). If you've got SQL Server 2012, it's very easy: import-module sqlps. For 2008, you need to add a Snap-In, add-pssnapin SqlServerCmdletSnapin. And since you have sqlcmd.exe, the PowerShell components should be there already.
If all else fails, go the System.Data.SQLClient route:
$Conn=New-Object System.Data.SQLClient.SQLConnection "Server=YOURSERVER;Database=TestDB;User Id=$user;password=$pw";
$Conn.Open();
$DataCmd = New-Object System.Data.SqlClient.SqlCommand;
$MyQuery = get-content "e:\SQLQuery1.sql";
$DataCmd.CommandText = $MyQuery;
$DataCmd.Connection = $Conn;
$DAadapter = New-Object System.Data.SqlClient.SqlDataAdapter;
$DAadapter.SelectCommand = $DataCmd;
$DTable = New-Object System.Data.DataTable;
$DAadapter.Fill($DTable)|Out-Null;
$Conn.Close();
$Conn.Dispose();
$DTable;
With both this and Invoke-SQLCmd, you'll be able to use try/catch to pick up and handle any error that occurs.
As seen in this question's answers, there is a method built into Powershell to invoke SQLCMD called, unsurprisingly, Invoke-Sqlcmd.
It's very easy to use for individual files:
Invoke-sqlcmd -ServerInstance $server -Database $db -InputFile $filename
Or groups:
$listOfFiles | % { Invoke-sqlcmd -ServerInstance $server -Database $db -InputFile $_ }
Use invoke-sqlquery module, available at this website.

How to connect to SQL Server LocalDB using Invoke-Sqlcmd?

I have sqlcmd.exe from both SQLServer 2008 and SQLServer 2012:
PS C:\> Get-Command sqlcmd.exe
Definition
----------
C:\Program Files\Microsoft SQL Server\100\Tools\Binn\SQLCMD.EXE
C:\Program Files\Microsoft SQL Server\110\Tools\Binn\SQLCMD.EXE
By modifying $env:PATH i force the use of sqlcmd.exe from SQL Server 2012:
PS C:\> $env:PATH = ($env:PATH -split ";" | Where-Object { $_ -notlike "*\Microsoft SQL Server\100\*" }) -join ";"
PS C:\> Get-Command sqlcmd.exe
Definition
----------
C:\Program Files\Microsoft SQL Server\110\Tools\Binn\SQLCMD.EXE
The default instance of LocalDB is up and running, and owned by the current user:
PS C:\> sqllocaldb i v11.0
Name: v11.0
Version: 11.0.2318.0
Shared name:
Owner: DOMAIN\me
Auto-create: Yes
State: Running
Last start time: 12/06/13 18:17:57
Instance pipe name: np:\\.\pipe\LOCALDB#08EDBEF0\tsql\query
Now, i can execute command on (localdb)\v11.0 using sqlcmd.exe
PS C:\> sqlcmd.exe -S "(localdb)\v11.0" -Q "select 1"
-----------
1
But when trying the same with Invoke-Sqlcmd i get a connection error:
PS C:\> Import-Module sqlps
PS C:\> Invoke-Sqlcmd -ServerInstance "(localdb)\v11.0" -Query "select 1"
Invoke-Sqlcmd : 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: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)
What can i do to lmake Invoke-Sqlcmd connect to (localdb)\v11.0 ?
UPDATE
Invoke-Sqlcmd from the SqlServer module supports LocalDB:
Invoke-Sqlcmd -ServerInstance "(localdb)\v11.0" -Query "select 1"
Invoke-Sqlcmd -ConnectionString "Server=(localdb)\v11.0; Integrated Security=True" -Query "select 1"
# both will work
From what I know sqlcmd.exe uses a connection string that has all the information, whereas Invoke-Sqlcmd breaks that information down into different parameters. So you likely need to split (localdb) and v11.0 across ServerInstance, Database, and ComputerName. Or you can use the ConnectionString parameter instead.
Now, I am not familiar with your use of localdb so there might be something funky with the way it handles that...or something.
Got this from a couple other sources, seems to work so far.
JBs Powershell
and
How can I run PowerShell with the .NET 4 runtime?
Another way of making PowerShell and LocalDB play nice is to make PowerShell aware of DOTNET 4.0.3. This can be done by creating a file called "powershell.exe.config" in the C:\Windows\System32\WindowsPowerShell\v1.0 . The file should contain the following:
<?xml version="1.0"?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0.30319"/>
<supportedRuntime version="v2.0.50727"/>
</startup>
</configuration>
Be aware that this not an officially supported way of using PowerShell, so it might break other stuff ...
I have been doing this at work recently and had some initial troubles connecting to a local Database. To get it to work, I ran the following code;
C:\> Import-Module sqlps -DisableNameChecking
SQLSERVER\:> cd ".\SQL\$(hostname)"
SQLSERVER\:> Invoke-Sqlcmd -Username "user" -Password "pass" -Database "databasename" -Query "foobar"
This worked for me and I was able to query the database. Obviously, change the Username, Password and Database parameter details to whatever the name of your database on the SQL Instance is called.
This is code that works for me under adverse conditions (see my comments just after the code). I suspect that simpler code may work in a more common environment, but I haven't dug into it.
The instance pipe times out after a few minutes. You're better off if you can connect using (localdb)\instanceName, because those connections don't seem to time out.
function Get-InstancePipeName ([string] $localDbName)
{
while (!($pipeName = ((sqllocaldb info $localDbName) -match 'instance pipe name').Split(':', 2)[1].Trim()))
{
sqllocaldb start $localDbName | Out-Null
}
return $pipeName
}
$scsb = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
$scsb.psbase.DataSource = Get-InstancePipeName localDbName # <== put your db name here
$sc = New-Object System.Data.SqlClient.SqlConnection $scsb.ConnectionString
$smoSc = New-Object Microsoft.SqlServer.Management.Common.ServerConnection $sc
$smoSvr = New-Object Microsoft.SqlServer.Management.Smo.Server $smoSc
Invoke-Sqlcmd -ServerInstance $smoSvr -Query 'select 1'
For reasons currently outside my control, the execution environment where this runs is unusual. It's a remote execution environment with an incomplete session context. Also, I had to redefine USERPROFILE to work around some other issues.
[later edit: I recently found a way to extend the timeout - I had to add a RECONFIGURE after the 2nd sp_configure and (as recommended) stop and start the localdb to get it to take effect)]
I'm guessing that invoke-sqlcmd doesn't know what "(localdb)" is. Try using localhost instead.