Problem sending ftp file using SQL Server stored procedure - sql-server

I am trying to send a file by ftp using a stored procedure in SQL Server.
When I run the procedure I get;
'OPTS':command not implemented.
after the first line of the script and then;
PORT address does not match originator.
after the 'put' command.
The stored procedure is;
ALTER PROCEDURE [dbo].[TestFTP]
#FTPScriptFile nvarchar(128)
AS
SET NOCOUNT ON;
BEGIN
DECLARE #FTPCommand nvarchar(256)
Set #FTPCommand = 'ftp -s:' + #FTPScriptFile
EXEC master..xp_cmdshell #FTPCommand
END
RETURN
The ftp script file contains;
open ftp.jht.co.uk 21
username
password
binary
put "D:\TestFiles\SampleFile.csv"
disconnect
bye
Any idea as to the problem?
Thanks for taking the time to look and any help would be greatly appreciated.

I decided to take a look at the first suggestion which was to use Powershell instead and that worked.
I created a stored procedure to create a script file;
$Directory = "D:\TestFiles"
$filename = "Example.csv"
$fullfile = "D:\TestFiles\Example.csv"
$ftpserver = "ftp://ftp.jht.co.uk/"
$username = "user"
$password = "pwd"
$ftpserverURI = New-Object -TypeName System.Uri -ArgumentList $ftpserver, [System.UriKind]::Absolute
$webclient = New-Object System.Net.WebClient
$webclient.Credentials = New-Object -TypeName System.Net.NetworkCredential -ArgumentList $username, $password
$uri = New-Object -TypeName System.Uri -ArgumentList $ftpserverURI, $filename
$webclient.UploadFile($uri, [System.Net.WebRequestMethods+Ftp]::UploadFile, $fullfile)
And then ran the script with powershell;
DECLARE #Command nvarchar(256)
Set #Command = 'powershell "' + #ScriptFile + '"'
EXEC master..xp_cmdshell #Command
Thanks to everyone who took a look and suggested a solution.

Related

Invoke-sqlcmd wait for Restoration to complete before going to next task

I'm running this powershell script in order to restore a database on sql server.
$Query = #" EXEC msdb.dbo.sp_start_job N'Job'; GO "#
Invoke-Sqlcmd -ServerInstance .... -Query $Query -Verbose
I get this output => VERBOSE: 'job' started successfully.
When I check SQL server I find that the Database in restoring state but didn't finish.
My problem is that I need to make sure that the database was successfully restored before passing to the next tasks.
I made a deep research on how to do it using other methods, like using the dbo.restorehistory to get the result but it's not really efficient.
Is there a way to make Invoke-sqlCmd command wait for the job to finish before continuing the execution of the script?
I found a solution to make sure that the restauration completed successfuly by using the stored procedure sp_help_jobhistory , here's the link for documentation: https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-help-jobhistory-transact-sql?view=sql-server-ver15.
Here's the script:
[string]$output;
$Query =#"
USE msdb;
GO
declare #temp table
(
job_id varchar(255),
job_name varchar(255),
run_status varchar(255),
run_date varchar(255),
run_time varchar(255),
run_duration varchar(255),
operator_emailed varchar(255),
operator_netsent varchar(255),
operator_paged varchar(255),
retries_attempted varchar(255),
server varchar(255)
);
INSERT #temp Exec dbo.sp_help_jobhistory #job_name = N'JobName';
select top 1 run_status from #temp order by run_date Desc, run_time ;
GO
"#
$output = Invoke-Sqlcmd -ServerInstance xxxxxx -Database xxxxx -Username xxx -Password xxxx -Query $Query
$result = $output.itemArray
if ($result -eq '1'){
Write-Host "Restauration succeded"
}
else { Write-Host "Restauration failed"}
I used a temporary table to read the content of the stored procedure, then I got the last job executed by ordering the result of the select query.
Also I added this script in a step before to test the restauration completion, because I can't access the database while it is in the restoration phase.
[string]$sucess = 'false'
$i = 0;
$ErrorActionPreference= 'silentlycontinue'
$query =#"
SELECT * FROM tiers Where id = 1;
GO
"#
do{
Invoke-Sqlcmd -ServerInstance xxxxxx -Database xxxxx -Username xxx -Password xxxx -Query $query
if($?){
$sucess = 'true';
break;
}
Write-Host "Echec"
start-sleep -Seconds 60
$i++
} While ($i -le 2 )
if ($sucess -eq 'true'){
Write-Host "Success"
}
Else {
Write-Host "Wait for too long, you need to manually check the restauration steps"
}
You can simply add the do...While in the first script, in my case I need to test the completion of restauration seperately from the script that checks the status of it.

How do I log errors from a powershell script called by a SQL stored procedure into a SQL table?

I have a SQL stored procedure that executes a powershell file, and I want to log any errors that occur executing the powershell file into a SQL table.
My SQL stored procedure:
CREATE PROCEDURE [dbo].[sp_RemoveEmptyFiles]
#filePath varchar(260)
AS
DECLARE #sql as varchar(4000)
DECLARE #powershellFileLocation varchar(260)
SET #powershellFileLocation = '\\MyComputerName\Files\Powershell\cleandirectory.ps1'
SET #sql = 'powershell -c "& { . ' + #powershellFileLocation + '; clean-directory ' + #filePath + ' }"'
EXEC master..xp_cmdshell #sql
My powershell script:
function clean-directory {
param ([string]$path)
try
{
if ($path.Length -le 0 -or -not (test-path -literalPath $path)) {
throw [System.IO.FileNotFoundException] """$path"" not a valid file path."
}
#
#
# Clean directories here
#
#
}
catch
{
write-host $error
}
}
Right now, if the script is successful, it returns an output of NULL and a Return Value of 0. The goal is to replace that catch block with something that will save those errors to a SQL table.
My first (inefficient) thought is to then invoke a SQL command in that catch block, something like:
$commandText = "INSERT INTO ErrorLogTable (TimeStamp, ErrorMessage) VALUES ($(Get-Date), $error)"
$command = $conn.CreateCommand()
$command.CommandText = $commandText
$command.ExecuteNonQuery()
But this hardly seems like the best way to do it-- connecting back to the SQL server the stored procedure was called from and creating a new command, etc. It should be noted that the powershell script, file path argument of the stored procedure, and SQL server are in different locations, so I do need to keep permission issues in mind (and hence why I am trying to avoid calling Invoke-Sqlcmd from my powershell script).
Is there a way to get the output of the powershell file in the stored procedure, and then save the error message into a table from there?
Since I'm using xp_cmdshell, I can capture that output by changing the SQL script as follows:
SET #sql = 'powershell -c "& { . ' + #powershellFileLocation + '; clean-directory ' + #filePath + ' }"'
-- table to hold the output from the cmdshell
CREATE TABLE #PowershellOutput ([Output] varchar(1000))
-- execute actual powershell script
INSERT INTO #PowershellOutput ([Output]) EXEC master..xp_cmdshell #sql
This captures each line sent to the console as a separate row. It's not the best solution for capturing error messages from powershell, so I'm still looking for a better way to capture them. I'll either join these rows to a single output, or find a better way to capture only the powershell error (as opposed to the entire stacktrace).

Powershell restore SQL Server database to new database

I have a database $CurrentDB and I want to restore a backup of $CurrentDB to $NewDB. The T-SQL command looks like this:
USE [master]
ALTER DATABASE [NewDB]
SET SINGLE_USER WITH ROLLBACK IMMEDIATE
RESTORE DATABASE [NewDB]
FROM DISK = N'D:\Backups\CurrentDB.bak'
WITH FILE = 1,
MOVE N'CurrentDB' TO N'D:\Databases\NewDB.mdf',
MOVE N'CurrentDB_log' TO N'D:\Logs\NewDB_log.ldf',
NOUNLOAD, REPLACE, STATS = 5
ALTER DATABASE [NewDB]
SET MULTI_USER
GO
I am attempting to user Restore-SqlDatabase but I don't know how to properly -RelocateFile
$CurrentDB = "CurrentDB"
$NewDB = "NewDB"
$NewDBmdf = "NewDB.mdf"
$CurrentDBlog = "CurrentDB_log"
$NewDBldf = "NewDB_log.ldf"
$backupfile = $CurrentDB + "ToNewDB.bak"
$RelocateData = New-Object
Microsoft.SqlServer.Management.Smo.RelocateFile($CurrentDB, $NewDBmdf)
$RelocateLog = New-Object
Microsoft.SqlServer.Management.Smo.RelocateFile($CurrentDBlog, $NewDBldf)
Restore-SqlDatabase -ServerInstance $SQLServer -Database $NewDB -BackupFile
$backupfile -ReplaceDatabase -NoRecovery -RelocateFile #($RelocateData,
$RelocateLog)
I can't seem to locate an example of what I am attempting to do. I have seen plenty of examples of restoring databases with the same name but different files. I want a different name and different file names. I am open to suggestions.
You don't have to use SMO just because your're in PowerShell.
import-module sqlps
$database = "NewDb"
$backupLocation = "D:\Backups\CurrentDB.bak"
$dataFileLocation = "D:\Databases\NewDB.mdf"
$logFileLocation = "D:\Logs\NewDB_log.ldf"
$sql = #"
USE [master]
ALTER DATABASE [$database]
SET SINGLE_USER WITH ROLLBACK IMMEDIATE
RESTORE DATABASE [$database]
FROM DISK = N'$backupLocation'
WITH FILE = 1,
MOVE N'CurrentDB' TO N'$dataFileLocation',
MOVE N'CurrentDB_log' TO N'$logFileLocation',
NOUNLOAD, REPLACE, STATS = 5
ALTER DATABASE [$database]
SET MULTI_USER
"#
invoke-sqlcmd $sql
And if you don't have sqlps installed, you can use System.Data.SqlClient from Powershell to run TSQL.
$RelocateData = [Microsoft.SqlServer.Management.Smo.RelocateFile]::new($CurrentDB, $NewDBmdf)
$RelocateLog = [Microsoft.SqlServer.Management.Smo.RelocateFile]::new($CurrentDBlog, $NewDBldf)
Restore-SqlDatabase -ServerInstance $SQLServer -Database $NewDB -BackupFile $backupfile `
-ReplaceDatabase -NoRecovery -RelocateFile #($RelocateData, $RelocateLog)

How to delete SQL Server databases trough vNEXT build task properly

We have a PowerShell cleanup script for our test machines:
$sqlConnection = new-object system.data.SqlClient.SqlConnection("Data Source=.\SQLExpress;Integrated Security=SSPI;Initial Catalog=master")
try {
$sqlConnection.Open()
$commandText = #"
exec sp_msforeachdb 'IF ''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'')
BEGIN
drop database [?]
END'
"#
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand
$sqlCommand.CommandText = $commandText
$sqlCommand.Connection = $sqlConnection
$SQLCommand.CommandTimeout = 0
$sqlCommand.ExecuteNonQuery()
}
finally{
$sqlConnection.Close()
}
Normally it works, but sometimes it cannot delete databases, since there seem to be some open connections and the build task fails to delete the databases as they are in use.
This also seems to occur at "some point" or "random".
Any advice to enhance the script?
(using lates TFS 2017 on prem and SQL Server 2014)
If you need to cut off all users with no warning, set the database offline before dropping it.
$commandText = #"
exec sp_msforeachdb 'IF ''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'')
BEGIN
alter database [?] set offline with rollback immediate;drop database [?];
END';
"#
if found a script here:
Drop all databases from server
-- drops all user databases
DECLARE #command nvarchar(max)
SET #command = ''
SELECT #command = #command
+ 'ALTER DATABASE [' + [name] + '] SET single_user with rollback immediate;'+CHAR(13)+CHAR(10)
+ 'DROP DATABASE [' + [name] +'];'+CHAR(13)+CHAR(10)
FROM [master].[sys].[databases]
where [name] not in ( 'master', 'model', 'msdb', 'tempdb');
SELECT #command
EXECUTE sp_executesql #command
it works as intended, still thanks for your help
Might I suggest using SMO?
push-location;
import-module sqlps -disablenamechecking;
pop-location
$serverName = '.';
$server = new-object microsoft.sqlserver.management.smo.server $servername;
foreach ($db in $server.Databases | where {$_.IsSystemObject -eq $false}) {
$server.killDatabase($db.Name);
}

SQL - How to determine if host name matches a known alias name

Below is a script I created to determine if the machine executing this code matches to a known server alias. However, I need to find a way to get this code into a function. Is there a more standard way of doing this without resorting to CLR?
My reason for asking is that I have a development machine and a production machine. If I'm running a certain bit of code on the development machine I want to do one thing; if I run it from the production server, I want something else to happen.
DECLARE #cmd nvarchar(2000)
DECLARE #runtimeHost nvarchar(128)
DECLARE #devServerAlias nvarchar(128)
DECLARE #results table ( [output] bit )
SET #runtimeHost = HOST_NAME() -- this would resolve to the non-FQDN machine name
SET #devServerAlias = 'serveralias' -- a known non-FQDN alias for what we think is the same machine as #runtimeHost
--original PowerShell script:
--PowerShell.exe -noprofile -executionpolicy bypass -command "& {foreach ($a in ([Net.DNS]::GetHostEntry(\"hostname.domain.com\")).addresslist.ipaddresstostring) { if ($a –eq [Net.DNS]::GetHostEntry(\"aliasname.domain.com\").addresslist.ipaddresstostring) { $true } else { $false } }}"
--The PowerShell resolves the IP address(es) and compares to find a match
SET #cmd = 'xp_cmdshell ''PowerShell.exe -noprofile -executionpolicy bypass -command "& {foreach ($a in ([Net.DNS]::GetHostEntry(\"'
+ #runtimeHost
+ '.domain.com\")).addresslist.ipaddresstostring) { if($a –eq [Net.DNS]::GetHostEntry(\"'
+ #devServerAlias
+ '.domain.com\").addresslist.ipaddresstostring) { $true } else { $false } }}"'''
-- Execute the Powershell. The result comes as two lines, the first being the answer and the second being a NULL entry.
-- Because of this, we will need to store the result in a table
INSERT INTO #results ( [output] )
EXEC sp_executesql #cmd
SELECT TOP 1 [output] from #results

Resources