Load text into SQL Server via Powershell - sql-server

I am loading character data read from a file row by row into a SQL table. The table is:
CREATE TABLE [dbo].[PSFileOrder](
[PSFOrder_Id] [int] IDENTITY(0,1) NOT NULL,
[PSFile] [varchar](255) NOT NULL,
CONSTRAINT [PK_PSFileOrder] PRIMARY KEY CLUSTERED
(
[PSFOrder_Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
The powershell code I am using is
#LoadPSFile
$PSFile = "d:\ps\xx.ps1"
cls
$xy = Get-content $PSFile
$xy
foreach($xy in $xy) {invoke-sqlcmd -serverInstance localhost\sqlexpress -query "Insert AA.dbo.PSFileOrder(PSFile) Values ('$xy')"}
If the file I am loading is:
#Filename
#The first line will always be the file name
cls
$filter = "*.*"
$folder = "\\localhost\d$\Master\men and their toys"
$filecount = (Get-ChildItem -literalpath $folder -filter $filter).Countem -literalpath $folder -filter $filter).Countem -literalpath $folder -filter $filter).Countem -literalpath $folder -filter $filter).Count
If ($filecount -lt 1) {$filecount = 0}
"There are {0} files in the {1} directory" -f $filecount, $folder
the file loads without error. If there is a single quote in the text
"There are {0} files in the {1} d'irectory" -f $filecount, $folder
then I will recieve this error
Invoke-Sqlcmd : Incorrect syntax near 'irectory'.
Unclosed quotation mark after the character string ' -f $filecount, $folder')
;'.
At C:\Users\RC\AppData\Local\Temp\2ed13f21-5b46-4df4-a5ee-2488c3dd5ee4.ps1:6 char:35
+ foreach($xy in $xy) {invoke-sqlcmd <<<< -serverInstance localhost\sqlexpress -query "Insert AA.dbo.PSFileOrder(PSFile) Values ('$xy')"}
+ CategoryInfo : InvalidOperation: (:) [Invoke-Sqlcmd], SqlPowerShellSqlExecutionException
+ FullyQualifiedErrorId : SqlError,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
I do believe the error is caused by the way I have written the "-query" statement using single quotes arount the $xy variable.
My questions are:
Have I crafted the Invoke-sqlcmd correctly?
Is there a substitue for the single quote around $xy?
Is there a better way to load the text?
I want to maintain the one to one relationship of the text file and row in the db.
Thanks
RC
Two answers put me on the correct path of doubling the single quote prior to loading to SQL. The revised code is
#LoadPSFile
cls
Get-content "d:\ps\xx.ps1" |
Foreach-Object {$_ -replace "'", "''"} |
ForEach-Object{invoke-sqlcmd -serverInstance localhost\sqlexpress -query "Insert AA.dbo.PSFileOrder(PSFile) Values ('$_')"}

You get malformed SQL syntax. I believe the quick fix is to replace any single quote with two single quotes in $xy:
$escaped = $xy.Replace("'", "''")
invoke-sqlcmd -serverInstance localhost\sqlexpress -query "Insert AA.dbo.PSFileOrder(PSFile) Values ('$escaped')"
# or (the same but without the intermediate variable):
invoke-sqlcmd -serverInstance localhost\sqlexpress -query #"
Insert AA.dbo.PSFileOrder(PSFile) Values ('$($xy.Replace("'", "''"))')
"#
But this is perhaps not the best way. Unfortunately I am not familiar with invoke-sqlcmd capabilities. If it supports parameterized queries I would go that way and supply $xy value as it is as a parameter value (no preparation of $xy would be needed in such a case).

You must double the apostrophes within the string
foreach($xy in $xy)
{
$xy = $xy -replace "'", "''"
invoke-sqlcmd -serverInstance localhost\sqlexpress -query "Insert AA.dbo.PSFileOrder(PSFile) Values ('$xy')"
}
when performance is an issue, you choose some bulkcopy solultion. In the SQLPSX project
there is a module adolib.psm1 which contains a function invoke-bulkcopy
link text

A better solution would be to use ADO.NET paramaterized queries via the SqlCommand.Parameters property. This will solve your single apostrophe error as well as scrub your input for anything else that could create a sql error.
Unfortunately Invoke-SqlCmd does not allow you to do this. However, I modified Chad Miller's Invoke-SqlCmd2 to allow you to do this. Include Invoke-SqlCmd2.ps1 in your profile and change your script like so:
foreach($xy in $xy) {invoke-sqlcmd -serverInstance localhost\sqlexpress -query "Insert AA.dbo.PSFileOrder(PSFile) Values (#xy)" -SqlParamaters #{ '#xy' = $xy}}

Related

Generate Scripts for Tables and all Extended Properties

Looking for a way to generate individual scripts for each table and include any relationships or extended properties. (After getting this working I will try to generate scripts for stored procedure, and function)
I am aware of the Sql-Server UI Generator (Database=>Tasks=>Generate Scripts) but that does one big file, not individuals. If there is a way to make this produce individual files (with out doing them 1 at a time) that would be best.
I have used Powershell package DBATools with some limited success. The following will make a file that contains create scripts for the table and the table's extended property but not the column extended properties.
$server = "sql01"
$database = "MyDatabase"
$table = "MyTable"
Get-DbaDbTable -SqlInstance $server -Database $database -Table $table | Export-DbaScript -FilePath ($database + "\" + $table +".sql")
Get-DbaDbTable -SqlInstance $server -Database $database -Table $table | Get-DbaExtendedProperty | ForEach-Object { Export-DbaScript -InputObject $_ -FilePath ($database + "\" + $table +".sql") -Append }
The answer was given by Larnu in the commnets.
The comment pointed out the option to save scripts individually and I have been over looking it for years.
While you found the option in SSMS, I wanted to say that this is also possible using the dbatools approach you tried. The "secret sauce" is specifying a ScriptingOptions object that controls the scripting behavior.
$so = New-DbaScriptingOption;
$so.ExtendedProperties = $true;
foreach ($table in Get-DbaDbTable -SqlInstance . -Database AdventureWorks2019){
$path = '{0}.{1}.sql' -f $table.Schema, $table.Name;
Export-DbaScript -InputObject $table -FilePath $path -ScriptingOptionsObject $so;
}

Invoke-Sqlcmd : Duplicate column names are not permitted in SQL PowerShell

I've spitted Glenn Berry's Performance Query in 14 queries but one of them (number 9) is not working.
This is my PowerShell code:
#Provide SQLServerName
$SQLServer ="localhost"
#Provide Database Name
$DatabaseName ="master"
#Prompt for user credentials
$credential = Get-Credential
$Query1= "-- 09 Index Sizes
-- Note: THIS IS SLOW as it reads index blocks. SAMPLED is not that high, but watch for prod I/O impact if using 'DETAILED'
SELECT DB_NAME() AS DatabaseName,
Object_name(i.object_id) AS TableName
,i.index_id, name AS IndexName
,i.type_desc
,ips.page_count, ips.compressed_page_count
,CAST(ips.avg_fragmentation_in_percent as DECIMAL(5,1)) [fragmentation_pct]
,CAST(ips.avg_page_space_used_in_percent as DECIMAL(5,1)) [page_space_used_pct]
,ips.index_depth, ips.page_count ,ips.forwarded_record_count, ips.record_count
FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, 'SAMPLED') AS ips -- or SAMPLED
INNER JOIN sys.indexes AS i ON ips.object_id = i.object_id AND ips.index_id = i.index_id
where ips.page_count > 1
ORDER BY ips.record_count desc;"
Invoke-Sqlcmd -Database $DatabaseName -ServerInstance $Server -Credential $credential -Query $Query1 | Format-Table
The error returned says:
Invoke-Sqlcmd : Duplicate column names are not permitted in SQL PowerShell. To repeat a column, use a column alias for the duplicate
column in the format Column_Name AS New_Name.
At C:\Users\FrancescoM\Desktop\CSV\Test.ps1:23 char:1
+ Invoke-Sqlcmd -Database $DatabaseName -ServerInstance $Server -Crede ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : SyntaxError: (:) [Invoke-Sqlcmd], SqlPowerShellSqlExecutionException
+ FullyQualifiedErrorId : DuplicateColumnNameErrorMessage,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
Not sure what is the duplicate column because if I run the query called into the PowerShell script on SSMS I don't see any duplicate column:
Arrived at home I formatted the query in a decent way and thanks to Notepad++, after clicking on each column I found out that ips.page_count was called twice.
So there was indeed a column called twice.
Use the following parameter for the Invoke-Sqlcmd command.
-OutputSqlErrors $False
It will suppress the error.
Documentation:
https://learn.microsoft.com/en-us/powershell/module/sqlserver/invoke-sqlcmd?view=sqlserver-ps
You get this error when you have duplicate column names in resultset of the query.
invoke-sqlcmd : Duplicate column names are not permitted in SQL PowerShell
Suppose you used below query as to see result-set in your invoke-sqlcmd query-
select test, * from testtable
Now the test column will be duplicate in the result and invoke sqlcmd execution will fail.
To Resolve this use like this-
select test as testColumn, * from testtable

Using PowerShell to Run a SQL query then insert the results into a table

I have a small PowerShell script that runs a query on about 30+ servers which pulls the server name and which version of SQL Server is installed. I then want to insert that data into a table but can't quite figure out how to do that with the returned data set my query returns. This is my code so far
$SvrNameList = #( invoke-sqlcmd -serverinstance MyServer -Database MyDB -Query "SELECT ServerName FROM ServerNames WHERE [Enabled] = 1" ) | select-object -expand ServerName
foreach ( $i in $SvrNameList )
{
invoke-sqlcmd -ServerInstance $i -Query "SELECT ##ServerName AS ServerName, ##Version AS Version"
}
Any help is appreciated
First, add the instance names and versions into a hash table. After it's populated, you can do inserts into the result table. Like so,
$ht=#{} # Create empty hashtable
foreach ( $i in $SvrNameList ){
$r = invoke-sqlcmd -ServerInstance $i -Query "SELECT s=##ServerName, v=##Version" # Query server names and versions
$ht.Add($r.s, $r.v) # Add name and version into hashtable
}
# Enumerate the hashtable and generate insert commands
$ht.GetEnumerator() | % {
invoke-sqlcmd -ServerInstance foo -query "insert into t(s, v) values ('" + $_.value + "', '"+ $_.name +"');"
}

Powershell sql insert error data will be truncated removed data and still get error

I have a powershell script that I am trying to use to write data from a source .txt to an SQL table
Import-Csv $_.fullname -Header Z_AP_EXNUM,Z_AP_ID | ForEach-Object {Invoke-Sqlcmd `
-Database $database -ServerInstance $server -Username $uid -Password $pwd `
-Query "insert into $table VALUES ('$_.Z_AP_EXNUM','$_.Z_AP_ID')"
My actual code has about 293 headers/columns to insert. The error I receiving is that String or binary data will be truncated. The statement has been terminated. I went into one of my test files and removed all the data but a few standard fields and still receive these errors. I am not sure what could be causing the insert to fail.
Error:
Invoke-Sqlcmd : String or binary data would be truncated.
The statement has been terminated.
At C:\Users***\Desktop\script\scripy.ps1:79 char:111
+ Z_AP_STR_CSEM5_9,Z_AP_STR_NAME5_10,Z_AP_STR_SCOR5_10,Z_AP_STR_CSEM5_10 | ForEach-Object {Invoke-Sqlcmd <<<< `
+ CategoryInfo : InvalidOperation: (:) [Invoke-Sqlcmd], SqlPowerShellSqlExecutionException
+ FullyQualifiedErrorId : SqlError,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
Check the table schema, this error usually shows due to the difference of column definition type in the table and the powershell data type you want to store in it.

Error when inserting strings with $( with Invoke-SqlCmd in Powershell

Setup:
CREATE TABLE MyTest (TestCol1 nchar(5))
Test:
Following work:
Invoke-Sqlcmd -Database "databasename" -ServerInstance "hostname" -OutputSqlErrors $True -Query "INSERT INTO MyTest VALUES ('`$5')"
Invoke-Sqlcmd -Database "databasename" -ServerInstance "hostname" -OutputSqlErrors $True -Query "INSERT INTO MyTest VALUES ('(5')"
Following fails with the error below:
Invoke-Sqlcmd -Database "databasename" -ServerInstance "hostname" -OutputSqlErrors $True -Query "INSERT INTO MyTest VALUES ('`$(5')"
Invoke-Sqlcmd -Database "databasename" -ServerInstance "hostname" -OutputSqlErrors $True -Query "INSERT INTO MyTest VALUES ('`$`(5')"
Error:
Invoke-Sqlcmd :
At line:1 char:1
+ Invoke-Sqlcmd -Database "databasename" -ServerInstance "hostname" -Ou ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ParserError: (:) [Invoke-Sqlcmd], ParserException
+ FullyQualifiedErrorId : ExecutionFailureException,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
Aftersome research I found that $( provides functionality in powershell thus is reserved. However I tried escaping the parenthesis but with no success. I've tried finding alternatative. Any ideas on how I can do this? If I use the CHAR function in SQL Server that works but it would be a pain to deal with in my code. Thank you.
All you need to do is disable the variables lookup in the invoke-Sqlcmd by adding -DisableVariables.
$() is use to pass in variable into the sql statements.
Invoke-Sqlcmd -InputFile $fileName -ServerInstance $serverInstance -DisableVariables
Source http://msdn.microsoft.com/en-us/library/cc281720.aspx
try this:
'INSERT INTO MyTest VALUES ("$(5")'
or this:
'INSERT INTO MyTest VALUES (''$(5'')'
The $(anyvalue) are reserved variables to be used with SQLCMD.EXE
Change the symbol $ by the sql server CHAR(36):
Invoke-Sqlcmd -Database "databasename" -ServerInstance "hostname" -OutputSqlErrors $True -Query "INSERT INTO MyTest VALUES (CHAR(36)+'(5)')"
The issue seems that the $( is a reserved word in Powershell thus to make this work I had use the CHAR function to store the $ (CHAR(36)). Once I did this it worked but for this project I abandoned using the Invoke-SqlCmd command and used a standard ADO.NET method. It seems that the Invoke-SqlCmd wants to parse the command and for the reserved combination continues to see the reserved word even if you escape the characters.
"INSERT INTO MyTest VALUES ('`$5')"
Outputs a string
INSERT INTO MyTest VALUES ('$5')
"INSERT INTO MyTest VALUES ('(5')"
Outputs a string
INSERT INTO MyTest VALUES ('(5')
"INSERT INTO MyTest VALUES ('`$(5')"
Outputs a string
INSERT INTO MyTest VALUES ('$(5')
"INSERT INTO MyTest VALUES ('`$`(5')"
Outputs a string
INSERT INTO MyTest VALUES ('$(5')
What do you want to insert? Actually '$5' or the value of the variable $5? Also, you have strange parentheses around your variable $5. Not sure if is suppose to be like that or your don't understand another important part of powershell variables? Because this a variable in a string "There are $((Get-Process).Count) processes running" too.

Resources