Powershell SMO: This method does not support scripting data - sql-server

I want to generate an import script for a MSSQL DB via Powershell (related to this question).
I tried doing this:
#Set-ExecutionPolicy RemoteSigned
$DB_NAME = "<<dbName>>"
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
$srv = new-object "Microsoft.SqlServer.Management.SMO.Server" "<<server>>"
$conContext = $srv.ConnectionContext
$conContext.LoginSecure = $false
$conContext.Login = "<<user>>"
$conContext.Password = "<<password>>"
$srv = new-object Microsoft.SqlServer.Management.Smo.Server($conContext)
$srv.SetDefaultInitFields([Microsoft.SqlServer.Management.SMO.View], "IsSystemObject")
$db = $srv.databases[$DB_NAME]
$scripter = new-object "Microsoft.SqlServer.Management.Smo.Scripter" $srv
$scripter.Options.ScriptSchema = $false
$scripter.Options.ScriptData = $true
$scripter.Options.ScriptDrops = $false
$scripter.Script($db)
But executing this throws an error:
"This method does not support scripting data"
I also tried to set the output file option but this doesn't change anything.
Can you tell me what I did wrong?
Thanks!

Per the error, Scripter.Script does not support scripting data. This is documented. What isn't documented is what you're supposed to use instead, but it's EnumScript:
$scripter.EnumScript(#($db.Tables))
You must pass the tables, since simply scripting the database will yield nothing (as, technically, the database itself contains no data, its tables do).
(The #() forcibly converts the Tables collection to an array, since that's what EnumScript expects.)

Related

PowerShell Return All Messages from SQL Query

I'm trying to query a SQL database and return only the messages that are produced, table results aren't important. I've looked at several answers online and came up with the following code, but every time I output the $message it is empty.
Can someone tell me what I'm doing wrong here or if there's some hidden problem?
$server = 'SampleServer'
$sqlQuery = "print 'sample message';"
$sqlConnection = new-object System.Data.SqlClient.SqlConnection("Server=$server; Trusted_Connection=True;")
$sqlConnection.FireInfoMessageEventOnUserErrors = $true
#Listener for messages
$handler = [System.Data.SqlClient.SqlInfoMessageEventHandler]{$message = "$($_)"}
$sqlConnection.add_InfoMessage($handler)
#tried using events, but it also didn't return anything. Assumed $Event was some system variable?
# Register-ObjectEvent -InputObject $sqlConnection -EventName InfoMessage -Action {$message = "$($Event.SourceEventArgs)"} -SupportEvent
#run Query
$sqlConnection.Open()
$sqlCommand = $sqlConnection.CreateCommand()
$sqlCommand.CommandText = $SQLQuery
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter $sqlcommand
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataSet) | out-null
$sqlConnection.Close()
#output $message results
write-host $message
Also, I can't use Invoke-SqlCommand.
EDIT So, after tinkering with the code some more, there's not really a problem with it, but there's a problem with how the query message is being returned from the $handler block. If I put a write-host in the $handler block it works just fine, but I need the information later in my script. I've tried these solutions to no avail (also added return $message to $handler):
$message = $sqlConnection.add_InfoMessage($handler) #nothing returned
$sqlConnection.add_InfoMessage($message = $handler) #just plain wrong
Is there a correct location to put an assignment in this situation?

Powershell Form - ListView - Populate Columns

I have designed a Powershell Gui Tool, the tool is designed works pretty well but I am stuck on how to populate the listview columns with the data from several xml files.
I have four column that I want populate
# ListView
$LvMain= New-Object System.Windows.Forms.Listview
$LvMain.Location = '300, 55'
$LvMain.Name = "ListViewInfo"
$LvMain.Size = "850, 600"
$LvMain.Text = "Pattern text"
$LvMain.Scrollable = $true
$LvMain.ContextMenuStrip = $ContextMenu
$LvMain.FullRowSelect = $True
$LvMain.GridLines = $True
$LvMain.UseCompatibleStateImageBehavior = $False
$LvMain.View = "Details"
$LvMain.font ="lucida console"
$LvMain.Controls.Add($ApplicationFormListViewInfo)
# Columns
$LvMain.Columns.Add("FileName")
$LvMain.Columns.Add("Folder")
$LvMain.Columns.Add("Line")
$LvMain.Columns.Add("Path")
It is meant when i hit the button " Run "
I will draw out the data from several xml files with
$GetXmlData = Get-ChildItem -Path $Path -Recurse -force -Include $FileTypes | Select-String -Pattern $Pattern
And populate the columns with the $Patterns Filename,Foldername,Line ( which line the pattern is ) and Path ( UncPath ).
However, a realy good tutorial on how listview works is hard to find.
Anyone who knows how to guide me to the right way ?

SQLServer connection with PowerShell Issue setting ConnectionTimeout Property

I'm connecting to SQLServer database using PowerShell. My script running a SQL code works fine but periodically it fails generating desired output and then running it again works fine.
$mySQLQuery = "SQL Query Here"
$myDataSet = New-Object System.Data.DataSet "myDataResultSet"
$sqlConn = New-Object System.Data.SqlClient.SqlConnection("Data Source=myDataSource;Initial Catalog=myDBServer;Integrated Security = False; User ID = UserName; Password =PassWord;")
$SqlConn.ConnectionTimeout= 0
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($mySQLQuery, $sqlConn)
$adapter.Fill($myDataSet)
$SqlConn.Close()
While, searching and reading I found that it has something to do with connection timeout. I tried to set sql connection timeout to 0 from default 30 but while running the scirpt I'm getting error saying below:
$sqlConn.ConnectionTimeout = 0 is read-only property.
Any idea what I am doing wrong here?
Thank you.
Personally, I set the Command Timeout, rather than the connection...
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = "<yourconnectionstring>"
$command = $connection.CreateCommand()
$command.CommandText = "<yoursql>"
$command.CommandTimeout = 0
$results = $command.ExecuteReader()
$table = New-Object System.Data.DataTable
$table.Load($results)
$connection.Dispose()
the easiest way to fix this is to add ;Connection Timeout=60 to your connect string.
$sqlConn = New-Object System.Data.SqlClient.SqlConnection('Connection Timeout=60')
$sqlConn.ConnectionTimeout
60
you probably don't want it set to 0, use 300 or something if you need a ridiculously high value.

Filling DataSet using SqlDataAdapter is changing the date format

I've searched all over the web and am unable to find a solution/guide for my problem.
I'm using the below bit of script its part of a larger script to export multiple SQL tables into CSVs. It fills a dataset with data from an SQL table.
The problem I have is in mainly in relation to datetime settings. For example, If I were to export the SQL table using the export wizard into a CSV file. The date appears exactly like it does in SQL e.g. "2014-05-23 07:00:00.0000000" or "2014-05-23".
However when I use my script it changes the format of the datetime to "23/05/2014 07:00:00" or "23/05/2014 00:00:00". I believe this has something to do with the culture settings of my machine/powershell session.
cls
# Declare variables for connection and table to export
$Server = 'server'
$Database = 'database'
$Folder = 'D:\Powershell Scripts\01_Export From SQL\Test Folder'
$Tablename1 = 'test'
$Tablename2 = ''
# Delcare Connection Variables
$SQLconnection = New-Object System.Data.SqlClient.SqlConnection
$SQLconnection.ConnectionString = "Integrated Security=SSPI;server=$Server;Database=$Database"
# Delcare SQL command variables
$SQLcommand = New-Object System.Data.SqlClient.SqlCommand
$SQLcommand.CommandText = "SELECT [name] from sys.tables where [name] like '%$Tablename1%' and [name] like '%$Tablename2%' order by [name]"
$SQLcommand.Connection = $SQLconnection
# Load up the Tables in a dataset
$SQLAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SQLAdapter.SelectCommand = $SQLcommand
$DataSet = New-Object System.Data.DataSet
$null = $SqlAdapter.Fill($DataSet)
$SQLconnection.Close()
"Time to Export`tRecords `tFile Name"
"--------------`t------- `t---------"
foreach ($Table in $DataSet.Tables[0])
{
$stopwatch = [system.diagnostics.stopwatch]::StartNew()
$FileExtractUTF8 = "$Folder\FromPSUTF8_$($Table[0]).csv"
$SQLcommand.CommandText = "SELECT * FROM [$($Table[0])]"
$SQLAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SQLAdapter.SelectCommand = $SQLcommand
$DataSet = New-Object System.Data.DataSet
$Count = $SQLAdapter.Fill($DataSet)
$SQLconnection.Close()
$DataSet.Tables[0] | Export-Csv $FileExtractUTF8 -NoTypeInformation -Encoding UTF8
$stopwatch.stop()
$Time = "{0}" -f $stopwatch.Elapsed.ToString('mm\:ss\.fff')
“{0,-14}`t{1,-10}`t{2}” -f $Time,$Count,$Table.name
}
The main goal is to export the data from SQL into a flat file with the data appearing exactly as it would if I used the export wizard.
Changing default DateTime format within a script
Within your DataSet / DataTable, the date exists as a [DateTime] type. When you export to CSV it needs to be converted to a string so it can be written to file. As you have observed, the default string conversion gives an output such as:
[PS]> (get-date).ToString()
21/03/2017 17:52:26
The format of this string is specified (as you worked out) by your specific globalization "culture". It appears to be a concatenation of the ShortDatePattern and LongTimePattern properties of the DateTimeFormat.
There are plenty of posts from people who have tried and failed to change the culture of their current session (i.e. the running PS host)...
why-does-powershell-always-use-us-culture-when-casting-to-datetime
...but it may be possible to change the globalization culture within your script using a mechanism such as the ones described here:
powershell-changing-the-culture-of-current-session
Using-Culture function
I suspect you should be able to use the Using-Culture example to wrap the Export-Csv line in your script.
But what culture to use?
So, you might now be able to set a specific culture, but do any of the pre-existing cultures use ISO8601 (sortable) dates? Or more specific formats? It seems not, so you have to make your own!
In short, you need to clone an existing CultureInfo and update the DateTimeFormat, specifically (at least) the ShortDatePattern to be what you want. Here is an example that I hope puts you on the right path. There are two functions, one which clones an existing CultureInfo and one which runs a command (Get-Date) with the new culture set for that thread.
function New-CultureInfo {
[CmdletBinding()]
[OutputType([System.Globalization.CultureInfo])]
param(
[Parameter()]
[System.Globalization.CultureInfo]
$BaseCulture = [System.Globalization.CultureInfo]::InvariantCulture
)
$CultureInfo = ($BaseCulture).Clone()
$CultureInfo.DateTimeFormat.ShortDatePattern = "yyyy'-'MM'-'dd"
$CultureInfo.DateTimeFormat.LongTimePattern = "HH:mm:ss.fff"
return $CultureInfo
}
function Test-DateFormat {
[CmdletBinding()]
[OutputType([System.DateTime])]
param(
[Parameter(Mandatory)]
[System.Globalization.CultureInfo]
$Culture
)
[System.Threading.Thread]::CurrentThread.CurrentUICulture = $Culture
[System.Threading.Thread]::CurrentThread.CurrentCulture = $Culture
(Get-Date).ToString()
}
Example use:
[PS]> $NewCulture = (New-CultureInfo)
[PS]> Test-DateFormat $NewCulture
2017-03-21 19:08:34.691
Now, I haven't been able to run this against an example close to the SQL problem in the OP, but I've had fun working this all out. ;-)
Great description by Charlie. My problem is that I wanted to change the default ToString to output an ISO datetime that contains a T separator between date and time instead of a space. TL;DR - it's not possible.
I'm more from the Java world than MS but had to write a script to export db CSVs and here's my investigation in case anyone else is interested in how the format is built. I dug into the source code to see how ToString works on DateTime.
According to the DateTime class it will defer to DateTimeFormat
https://referencesource.microsoft.com/#mscorlib/system/datetime.cs,0a4888bea7300518
public override String ToString() {
Contract.Ensures(Contract.Result<String>() != null);
return DateTimeFormat.Format(this, null, DateTimeFormatInfo.CurrentInfo);
}
This will eventually call into Format method with a null String format. This causes the block below to be called which basically specifies the "G" format to use for the date/time.
https://referencesource.microsoft.com/#mscorlib/system/globalization/datetimeformat.cs,386784dd90f395bd
if (offset == NullOffset) {
// Default DateTime.ToString case.
if (timeOnlySpecialCase) {
format = "s";
}
else {
format = "G";
}
}
This will eventually make a call to get the current Culture's DateTimeInfo object that has an internal String pattern that cannot be overridden. It lazily sets this so that it doesn't have to concatenate and there is no way to override it. As Charlie pointed out, it always concatenates ShortDatePattern and LongTimePattern with a space separator.
https://referencesource.microsoft.com/#mscorlib/system/globalization/datetimeformatinfo.cs,799bd6e7997c4e78
internal String GeneralLongTimePattern {
get {
if (generalLongTimePattern == null) {
generalLongTimePattern = ShortDatePattern + " " + LongTimePattern;
}
return (generalLongTimePattern);
}
}
So there you have it. Hope it helps the next person know why/how and that it's not possible to override the overall default format - just the date and the time components.

SQL Server SMO, shoud I invoke refresh everytime I modified something?

I'm playing with SMO and tried to use it to change the database owners to sa. The code is
# To simplify our discussing, let's say we have a function Get-SMOServer
$s = Get-SMOServer -Instance myserver\myinstance
$s.databases | ?{$_.owner -ne "sa"} | %{$_.setowner("sa", $true)}
At this point, when I check the database owners from SSMS, the owners were already changed. However, if I check it from $s.databases, I still got the old data, until I do something like:
$s.databases | %{$_.refresh()}
Then I can get the correct result from $s.databases.
I checked the SMO objects and found many of them have a refresh() function. My question is, should I call refresh() every time I modified some object? How to find all object types that have a refresh() member?
Thanks
You can look at the SMO assembly. I'm using SMO which ships with 2008 R2:
$assm = add-type -AssemblyName "Microsoft.SqlServer.Smo, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" -EA Stop -PassThru
$assm | %{$hasRefreshMethod = $null; $hasRefreshMethod = $_.GetMethods() | ?{$_.name -eq "Refresh"}; new-object psobject -property #{Name=$_.Name; HasRefreshMethod=$($hasRefreshMethod -ne $null)}}

Resources