Issue with scripting out subscriptions using Windows PowerShell - sql-server

I am trying to script out the replication objects via PowerShell using Microsoft.SqlServer.Rmo.dll. The replication type is transactional with push subscriptions.
I have been able to script out publication, articles, PALs but not able to script out publisher side subscriptions.
Reference
[reflection.assembly]::LoadFrom("c:\\sql\\Microsoft.SqlServer.Rmo.dll") | out-null
ScriptOptions
$scriptargs =[Microsoft.SqlServer.Replication.ScriptOptions]::Creation `
-bor [Microsoft.SqlServer.Replication.ScriptOptions]::IncludeCreateLogreaderAgent `
-bor [Microsoft.SqlServer.Replication.ScriptOptions]::IncludeCreateMergeAgent `
-bor [Microsoft.SqlServer.Replication.ScriptOptions]::IncludeCreateQueuereaderAgent `
-bor [Microsoft.SqlServer.Replication.ScriptOptions]::IncludePublicationAccesses `
-bor [Microsoft.SqlServer.Replication.ScriptOptions]::IncludeArticles `
-bor [Microsoft.SqlServer.Replication.ScriptOptions]::IncludePublisherSideSubscriptions` #one way tried to get the subscriptions
-bor [Microsoft.SqlServer.Replication.ScriptOptions]::IncludeGo
foreach($replicateddatabase in $repsvr.ReplicationDatabases)
{
if ($replicateddatabase.TransPublications.Count -gt 0)
{
foreach($tranpub in $replicateddatabase.TransPublications)
{
**[string] $myscript=$tranpub.script($scriptargs)** #Errors out here
writetofile $myscript $filename 0
}
}
}
The other way I tried is exclude IncludePublisherSideSubscriptions from Scriptoptions and tried to script out directly using the following statement
foreach($replicateddatabase in $repsvr.ReplicationDatabases)
{
if ($replicateddatabase.TransPublications.Count -gt 0)
{
foreach($tranpub in $replicateddatabase.TransPublications)
{
[string] $subs=$tranpub.TransSubscriptions.script($scriptargs) #another way but same error
writetofile $subs $filename 0
}
}
}
The third way I tried:
$repsvr.ReplicationDatabases.TransPublications.TransSubscriptions.Script($scriptargs)
Of all, I found the following link to be very helpful where my code is mostly based on but just got stuck in the scripting out of publisher side subscriptions. I appreciate your help.

Microsoft.SqlServer.Replication has been deprecated since SQL Server 2012 (I think), and as of SQL Server 2017, it's no longer available. Follow the link and try switching the version to 2017 or 2019.
If you're using server 2016 and below, you may just need to load the rmo assembly from the GAC (Global Assembly Cache), this will do that for you.
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.Rmo')
If for whatever reason, you don't want to, or can't use the GAC, do this
Add-Type -Path 'C:\path\to\dll\Microsoft.SqlServer.Rmo.dll'
I'll dig, and see if there's a different way to go about doing what you're trying to do.
Edit: Not sure if I'm misunderstanding the docs about RMO, or if they're just out of date
you take this link and it shows you how to configure publishing & distribution using RMO on Server 2019. Step 2 references this which shows the ReplicationServer class. The problem is, that's for SQL Server 2016. I'm not entirely sure if this is supported past 2016 or not.

Related

Convert dacpac into folder structure of database objects with powershell

I'm working on integrating SQL Server databases into our in-house version control/deployment utility, which is built with powershell,and uses Github as a repository.
Using the excellent sqlpackage.exe utility, I have been able to add a process whereby a developer can extract their current changes into a dacpac and store it in Github, then do the opposite in reverse when they want to get the latest version. However, because the .dacpac is a binary file, it's not possible to see differences in git. I have mitigated this somewhat by unzipping the dacpac before storing in in source control, so contained xml files are added instead. However, even though these files are text-based, they are still not easy to look through and find differences.
What I would like to do, is convert the dacpac into a folder structure similar to what would be seen in SSMS (with all the database objects such as triggers, sprocs etc in their respective folders), store that in Github, and then convert it back into a dacpac when a client checks out the code. However, there doesn't seem to be any function in sqlpackage.exe for this, and I can't find any documentation. Is there any command line tool I can use to this through Powershell?
Using the public APIs for DacFx you can load the dacpac, iterate over all objects, and script each one out. If you're willing to write your own code you could write each one to its own file based on the object type. The basic process is covered in the model filtering samples in the DacExtensions Github project. Specifically you'll want to do something like the ModelFilterer code that loads a dacpac, queries all objects, scripts them out - see the CreateFilteredModel method. I've put a sample that should mostly work below. Once you have this, you can easily do compare on a per-object basis.
using (TSqlModel model = new TSqlModel(dacpacPath))
{
IEnumerable<TSqlObject> allObjects = model.GetObjects(QueryScopes);
foreach (TSqlObject tsqlObject allObjects)
{
string script;
if (tsqlObject.TryGetScript(out script))
{
// Some objects such as the DatabaseOptions can't be scripted out.
// Write to disk by object type
string objectTypeName = tsqlObject.ObjectType.Name;
// pseudo-code as I didn't bother writing.
// basically just create the folder and write a file
this.MkdirIfNotExists(objectTypeName);
this.WriteToFile(objectTypeName, tsqlObject.Name + '.sql', script);
}
}
}
This can be converted into a powershell cmdlet fairly easily. The dacfx libraries are on nuget at https://www.nuget.org/packages/Microsoft.SqlServer.DacFx.x64/ so you should be able to install them in PS and then use the code without too much trouble.
Based on the other post I was able to get a script working. Caveat is you'll have to try the types till you get what you want... The way it is no it trys to put the full http or https value for some of the objects.
param($dacpacPath = 'c:\somepath' Debug', $dacpac = 'your.dacpac')
Add-Type -Path 'C:\Program Files (x86)\Microsoft SQL Server\120\DAC\bin\Microsoft.SqlServer.Dac.dll'
add-type -path 'C:\Program Files (x86)\Microsoft SQL Server\120\DAC\bin\Microsoft.SqlServer.Dac.Extensions.dll'
cd $dacpacPath
$dacPack = [Microsoft.SqlServer.Dac.DacPackage]::Load(((get-item ".\$dacpac").fullname))
$model =[Microsoft.SqlServer.Dac.Model.TSqlModel]::new(((get-item ".\$dacpac").fullname))
$queryScopes = [Microsoft.SqlServer.Dac.Model.DacQueryScopes]::All
$return = [System.Collections.Generic.IEnumerable[string]]
$returnObjects = $model.GetObjects([Microsoft.SqlServer.Dac.Model.DacQueryScopes]::All)
$s = ''
foreach($r in $returnObjects)
{
if ($r.TryGetScript([ref]$s))
{
$objectTypeName = $r.ObjectType.Name;
$d="c:\temp\db\$objectTypeName"
if(!(test-path $d ))
{
new-item $d -ItemType Directory
}
$filename = "$d\$($r.Name.Parts).sql"
if(! (test-path $filename))
{new-item $filename -ItemType File}
$s | out-file $filename -Force
write-output $filename
}
}

sqlsrv drivers slow in codeigniter?

I have installed the latest version of CI 2.1.3
Now after running a query, I am getting a very slow response time for something very simple such as:
function Bash(){
$sql = “SELECT * FROM Contacts”;
$stmt = sqlsrv_query( $conn, $sql );
if( $stmt === false) {
die( print_r( sqlsrv_errors(), true) );
}
after querying a remote database. (Sql server 2008)
When I run this same query in a simple PHP script against the same remote database. I get results instantly.
a) Has anyone else experienced this problem with the sqlsrv drivers in codeigniter?
If so, how did you solve it?
Here is my connection string:
$db['default']['hostname'] = "xxxxx,1433";
$db['default']['username'] = "xx";
$db['default']['password'] = "xxxxxx-xx";
$db['default']['database'] = "xxxxxxxxx";
$db['default']['dbdriver'] = "sqlsrv";
$db['default']['dbprefix'] = '';
$db['default']['pconnect'] = TRUE;
$db['default']['db_debug'] = TRUE;
$db['default']['cache_on'] = TRUE;
$db['default']['cachedir'] = '';
$db['default']['char_set'] = 'utf8';
$db['default']['dbcollat'] = 'utf8_general_ci';
$db['default']['swap_pre'] = '';
$db['default']['autoinit'] = TRUE;
$db['default']['stricton'] = FALSE;
UPDATE:
I have found the following from running the profiler.
DATABASE: database QUERIES: 1 (Hide)
0.0659 select * from Contacts
Loading Time: Base Classes 0.0428
Controller Execution Time ( Welcome / AzureBash ) 58.2173
Total Execution Time 58.2602
It seems as though the query is executing in 0.06 secs but the controller is taking a minute to load.
No idea why this is happening.
Solution
The active records interface for the latest SQLSRV drivers are buggy.
So, download and overwrite the existing interface with these (overwrite your sqlsrv folder in the database folder in CI):
http://www.kaweb.co.uk/blog/mssql-server-2005-and-codeigniter/
Note: These have been tested with SQL Azure and works.
$query->num_rows(); does not work with these drivers, so I suggest you use count instead. Or create your own wrapper.
In addition date is now a date object type in your result set.
I hope this helps.
Solution 2
If for whatever reason you find a bug that makes this completely unusable. Revert back to the sqlsrv interface originally provided. You will find what is causing the problem is the way the original interface are executing the query, thus, create a database helper class; use $sql = $this->db->last_query(); to get the query you was about to execute and then within the database_helper class execute it yourself:
function MakeDbCall ($sql)
{
$serverName = "xxxxx-xxxx-xxx,1433"; //serverName\instanceName
$connectionInfo = array( "Database"=>"xxx", "UID"=>"xx", "PWD"=>"xxxxx","ConnectionPooling" => "1");
$conn = sqlsrv_connect($serverName,$connectionInfo);
$stmt = sqlsrv_query($conn, $sql);
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC) ) {
$result_array[] = $row;
}
return $result_array;
}
Create one for row_array.
You should be able to call this function directly, from anywhere in your app. Whilst taking advantage of the way active_records constructs your query.
Not an ideal solution, but until codeigniter sort their SQLSRV class, there is not a lot we can do.
Adding an answer to this after the answer has already been accepted because I found a different solution. I was having the same problem ... looping through the result set was very very slow. i opened system/database/drivers/sqlsrv/sqlsrv_driver.php and found the connection function. i noticed that is was using the SQLSRV_CURSOR_STATIC option. i changed this to SQLSRV_CURSOR_CLIENT_BUFFERED and my slowness problems went away. See documentation for this here:
http://msdn.microsoft.com/en-us/library/hh487160(v=sql.105).aspx
I honestly have no idea what the sql server driver for php is doing, however, given the speed up, etc i can guess that the driver might be using a cursor by default. this seems like an awful idea. i also am assuming that by choosing client_buffered the data for the query would b e read without a cursor and accessed in memory on the client as if it were a cursor. If this is the case, bad things might happen if you try to execute a query that has many many rows to read. Perhaps another option (SQLSRV_CURSOR_FORWARD?) can be used to read data without a cursor - but i'm sure the methods used to access the query will be more limited (e.g. not using result_array())
-Don
Solution
The active records interface for the latest SQLSRV drivers are buggy.
So, download and overwrite the existing interface with these (overwrite your sqlsrv folder in the database folder in CI):
http://www.kaweb.co.uk/blog/mssql-server-2005-and-codeigniter/
Note: These have been tested with SQL Azure and works.
$query->num_rows(); does not work with these drivers, so I suggest you use count instead. Or create your own wrapper.
In addition date is now a date object type in your result set.
Solution 2
If for whatever reason you find a bug that makes this completely unusable. Revert back to the sqlsrv interface originally provided. You will find what is causing the problem is the way the original interface are executing the query, thus, create a database helper class; use $sql = $this->db->last_query(); to get the query you was about to execute and then within the database_helper class execute it yourself:
function MakeDbCall ($sql)
{
$serverName = "xxxxx-xxxx-xxx,1433"; //serverName\instanceName
$connectionInfo = array( "Database"=>"xxx", "UID"=>"xx", "PWD"=>"xxxxx","ConnectionPooling" => "1");
$conn = sqlsrv_connect($serverName,$connectionInfo);
$stmt = sqlsrv_query($conn, $sql);
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC) ) {
$result_array[] = $row;
}
return $result_array;
}
Create one for row_array.
You should be able to call this function directly, from anywhere in your app. Whilst taking advantage of the way active_records constructs your query.
Not an ideal solution, but until codeigniter sort their SQLSRV class, there is not a lot we can do.
What is your connection string? You can specify the "network protocol" explicitly, which somtimes can affect speed.
http://www.connectionstrings.com/articles/show/define-sql-server-network-protocol
"Provider=sqloledb;Data Source=190.190.200.100,1433;Network Library=DBMSSOCN;Initial Catalog=pubs;User ID=myUsername;Password=myPassword;"
By specifying the IP address, the port number (1433) and the Network Library, you are providing a very granular connection string.
Your details may vary of course.
Alot of times, you don't need this. But I've been on a few client trips where this was the magic dust.
You might want to turn db_debug to FALSE which should save time debugging the database.
Also, would suggest to turn cache_on to FALSE and specify cachedir and use $this->db->cache_on(); for queries that are less dynamic, i.e. does not change frequently.
For speed up fetch up to 3 times please use "MultipleActiveResultSets"=>'0' in your sqlsrv_connect connection options.
Ex:
$db = sqlsrv_connect('127.0.0.1', array('Database'=>'dbname','UID'=> 'sa','PWD'=> 'pass',"CharacterSet" =>"UTF-8","ConnectionPooling" => "1"
,"MultipleActiveResultSets"=>'0'
));

Automate turning named-pipes and tcp\ip on

I am working on an install of a new product that requires modifications to SQL Server.
Specifically, enable tcp/ip and turning on named pipes. I know how to do it manually. What i want is a way to automate this for a new customer though SQL or with C# code.
I would love any suggestions for the right direction.
You can use C# and Server Management Objects (SMO) to do it. The classes you need are in the Microsoft.SqlServer.Smo and Microsoft.SqlServer.WmiManagement libraries.
Here's a Powershell snippet I've used that uses the same objects. Hopefully, it will point you down the right path.
$smo = 'Microsoft.SqlServer.Management.Smo.'
$wmi = new-object ($smo + 'Wmi.ManagedComputer').
# List the object properties, including the instance names.
$Wmi
# Enable the TCP protocol on the default instance.
$uri = "ManagedComputer[#Name='<computer_name>']/ ServerInstance[#Name='MSSQLSERVER']/ServerProtocol[#Name='Tcp']"
$Tcp = $wmi.GetSmoObject($uri)
$Tcp.IsEnabled = $true
$Tcp.Alter()
$Tcp
# Enable the named pipes protocol for the default instance.
$uri = "ManagedComputer[#Name='<computer_name>']/ ServerInstance[#Name='MSSQLSERVER']/ServerProtocol[#Name='Np']"
$Np = $wmi.GetSmoObject($uri)
$Np.IsEnabled = $true
$Np.Alter()
$Np

How to deploy SQL CLR stored procedure to multiple servers

I have inherited a SQL CLR project as part of a code maintenance project that I'm working on for a client. I'm pretty new to SQL CLR, admittedly, so I'm trying to figure out how it works.
I noticed that the database connection string is stored in the project's Properties, so I know how to change it if I need to. The one question I have though is this: is it possible to set multiple connection strings for deployment to multiple SQL Server instances? In my case I have a local dev machine, a staging server, and a production server (with a separate copy of the target database on each server). I'd like to be able to deploy the SQL CLR assembly to all 3 without having to change the connection string and re-build for each one.
You should not deploy to anywhere but development via Visual Studio, hence the connection string in the Project should always point to your dev environment.
Once you have the code tested in the development server, you can script out the Assembly in SSMS by right-clicking on the Assembly in question and do "Script Assembly As..." then "Create To..." and then "New Query Window". This will give you the basic script that should be used to deploy to QA, Staging, and Production.
The general format is:
USE [DBName]
GO
CREATE ASSEMBLY [AssemblyName]
AUTHORIZATION [dbo]
FROM 0x0000...
WITH PERMISSION_SET = SAFE
You do not really need to propagate the Assembly Files to the other environments, though if you want to it does not hurt.
If you want to automate that, once you have that basic script you can always grab the updated Assembly code (what is noted as 0x0000 above) via:
SELECT Content FROM sys.assembly_files WHERE name = 'AssemblyName'
Edit:
For the sake of completeness, as Jeremy mentioned in a comment below, the above info only describes deployment of the Assembly itself, not of the wrapper objects to access the code within the Assembly. A full deployment process would:
Drop existing wrapper objects (Stored Procs, Functions, Triggers, Types, and Aggregates)
Drop the Assembly
Create the new Assembly
Create the wrapper objects
When you deploy the code to your development server, Visual Studio creates a .sql file in the bin/Release folder.
This can useful for deployment, it requires some cleaning.
Here is a perl script I'm using to get a deployment script from the script created by VS.
It's closely linked to my needs and the file format (I'm using VS 2010 SP1, SQL 2008 R2, perl within cygwin), consider this as an example it may not work automagically for everyone.
use strict;
use warnings;
use Text::Unidecode 'unidecode'; # http://search.cpan.org/dist/Text-Unidecode/
sub ProcessBlock($)
{
my $lines = $_[0];
if ($lines =~ "Deployment script for") { return 0; }
if ($lines =~ "^SET ") { return 0; }
if ($lines =~ "^:") { return 0; }
if ($lines =~ "^USE ") { return 0; }
if ($lines =~ "^BEGIN TRANSACTION") { return 0; }
if ($lines =~ "extendedproperty") { return 0; }
if ($lines =~ "^PRINT ") { return 0; }
if ($lines =~ "#tmpErrors") { return 0; }
if ($lines =~ "^IF \#\#TRANCOUNT") { return 0; }
my $drop = $lines;
if ($drop =~ m/^DROP (FUNCTION|PROCEDURE) ([^ ]+);/m)
{
printf("if OBJECT_ID('$2') IS NOT NULL\n");
}
elsif ($drop =~ m/^DROP ASSEMBLY \[([^ ]+)\];/m)
{
printf("IF EXISTS (SELECT 1 FROM sys.assemblies WHERE name = '$1')\n");
}
printf($lines);
printf("GO\n");
my $create = $lines;
if ($create =~ m/^CREATE PROCEDURE (\[[^]]+\])\.(\[[^]]+\])/m)
{
printf("GRANT EXECUTE ON $1.$2 TO PUBLIC\nGO\n");
}
elsif ($create =~ m/^CREATE FUNCTION (\[[^]]+\])\.(\[[^]]+\]).*RETURNS .* TABLE /ms)
{
printf("GRANT SELECT ON $1.$2 TO PUBLIC\nGO\n");
}
elsif ($create =~ m/^CREATE FUNCTION (\[[^]]+\])\.(\[[^]]+\])/m)
{
printf("GRANT EXECUTE ON $1.$2 TO PUBLIC\nGO\n");
}
}
my $block="";
while (<>)
{
my $line = $_;
$line = unidecode($line);
if ($line =~ "^GO")
{
ProcessBlock($block);
$block = "";
}
else
{
$block .= $line;
}
}
Usage:
perl FixDeploy.pl < YourAssembly.sql > YourAssembly.Deploy.sql
Look here: The difference between the connections strings in SQLCLR I think you should use context connection if possible. That way you don't have to reconfigure.
If you need different credentials or something, you can query a settings table that holds those settings. Use the context connection to connect, query the settings table to get the login details and then use them to connect again.
Also: the connection string is in the properties, but as I understand the settings.xml does not get deployed so you'd always be getting the default values hardcoded into settings class.

Assembling SSIS Packages in PowerShell

I should preface by saying my experience with scripting or programming in OOP languages is limited.
I'm working on a method for programatically creating and executing SSIS packages using PowerShell. Unfortunately, most of the resources available for PowerShell and SSIS are for calling PS from SSIS, not the other way around.
I have, however, found a number of resources for VB/C# for creating SSIS packages.
Example resource here.
I've succeeded in converting most of the code by calling the DTS/SSIS assemblies, but it's failing now on converting the TaskHost object to a mainpipe.
Sample code:
[Void][Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.ManagedDTS')
[Void][Reflection.Assembly]::LoadWithPartialName('Microsoft.Sqlserver.DTSPipelineWrap')
# Create the Package and application, set its generic attributes
$Package = New-Object Microsoft.SqlServer.Dts.Runtime.Package
$Package.CreatorName = $CreatorName
$App = New-Object Microsoft.SqlServer.Dts.Runtime.Application
# Set connection info for our package
$SourceConn = $package.Connections.Add("OLEDB")
$SourceConn.Name = "Source Connection"
$SourceConn.set_ConnectionString("Data Source=$SourceServer;Integrated Security=True")
$TargetConn = $package.Connections.Add("OLEDB")
$TargetConn.Name = "Target Connection"
$TargetConn.set_ConnectionString("Data Source=$TargetServer;Integrated Security=True")
# Build the tasks
# Data Flow Task - actually move the table
[Microsoft.SQLServer.DTS.Runtime.Executable]$XferTask = $Package.Executables.Add("STOCK:PipelineTask")
$XferTaskTH = [Microsoft.SqlServer.Dts.Runtime.TaskHost]$XferTask
$XferTaskTH.Name = "DataFlow"
$XferTaskTH.Description = "Dataflow Task Host"
$DataPipe = [Microsoft.SQLServer.DTS.pipeline.Wrapper.MainPipeClass]($XferTaskTH.InnerObject)
Everything works fine til the last line, when I get the error:
Cannot convert the
"System.__ComObject" value of type
"System.__ComObject#{}" to
type
"Microsoft.SqlServer.Dts.Pipeline.Wrapper.MainPipeClass"
Any assistance or ideas are welcome!
Microsoft.SqlServer.DTSPipelineWrap makes heavy use of COM instances.
This forum post suggested using CreateWRapperOfType method:
http://social.technet.microsoft.com/Forums/en-US/ITCG/thread/0f493a31-fbf0-46ac-a6a5-8a10af8822cf/
You could try this:
$DataPipe = [System.Runtime.InteropServices.Marshal]::CreateWrapperOfType($XferTaskTH.InnerObject, [Microsoft.SQLServer.DTS.pipeline.Wrapper.MainPipeClass])
Doesn't error out and produces an object--I'm not sure of what type.
You could always just compile the working .NET version you referenced above into an exe, and allow it to accept parameters as needed in order to create the SSIS packages. Then, use Powershell to call the executable with the parameters as needed.

Resources