How to fix Unclosed quotation mark in powershell [duplicate] - sql-server

I am trying to run the following query, which takes someone's name and attempts to insert it into an SQL Server database table.
$name = "Ronnie O'Sullivan"
$dataSource = "127.0.0.1"
$database = "Danny"
$connectionString = "Server=$dataSource;Database=$database;Integrated Security=True;"
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
$connection.Open()
$query = "INSERT INTO People(name) VALUES('$name')"
$command = $connection.CreateCommand()
$command.CommandText = $query
$command.ExecuteNonQuery()
$connection.Close()
The problem I am facing is that the single quote is causing an issue in my query. The query is being executed as
INSERT INTO People(name) VALUES('Ronnie O'Sullivan')
which causes an SQL syntax error.
My question is how do I escape my $name variable so that it renders on the SQL side.
One solution is to do a find and replace on my $name variable, find: ' replace: ''
$name.Replace("'", "''")
Is there a more elegant solution out there, or a function that I can't seem to find?
Thank you.

You can try to update your code to to use a parametrised value that will cope with quotes in a string:
$query = "INSERT INTO People(name) VALUES(#name)"
$command = $connection.CreateCommand()
$command.CommandText = $query
$command.Parameters.Add("#name", $name) -- | Out-Null (may be required on the end)
$command.ExecuteNonQuery()
I'm not experienced with powershell but referenced this post for a parametrised query:

Tanner's helpful answer is definitely the most robust and secure solution, because using a [parameterized / prepared statement (query) eliminates any possibility of a SQL injection attack.
However, in this constrained case, where you want to insert a value into a single-quoted SQL string ('...'), you can get away with simply doubling any embedded ' characters in the value:
$query = "INSERT INTO People(name) VALUES('$($name -replace "'", "''")')"
The above uses PowerShell's string interpolation via $(...), the subexpression operator, to embed an expression that uses the -replace operator to double all embedded ' instances in the value of $name.
Note: You could also use $name.Replace("'", "''") above, which performs better in this simple case, but PowerShell's -replace operator is generally preferable, not only for being PowerShell-native, but for offering superior abilities, because it is regex-based and supports array as its LHS - see this comment on GitHub.

Related

How to store a SQL Server query result to Powershell Array?

I am new to PowerShell scripting and currently working on a script to load the result of SQL Server query to store as a PowerShell array. Below is my code for reference. :
$SQLServer = 'MyServer';
$Database = 'Test';
## - Connect to SQL Server using non-SMO class 'System.Data':
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection;
$SqlConnection.ConnectionString = `
"Server = $SQLServer; Database = $Database; Integrated Security = True";
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand;
$SqlCmd.CommandText = $("select distinct Servername from dbo.tableA
where Servername like '%hw%'");
$SqlCmd.Connection = $SqlConnection;
$SqlCmd.CommandTimeout = 0;
## - Extract and build the SQL data object '$DataSetTable':
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter;
$SqlAdapter.SelectCommand = $SqlCmd;
$DataSet = New-Object System.Data.DataSet;
$SqlAdapter.Fill($DataSet);
$SqlConnection.Close()
$Servername = #[SqlAdapter]
I expect $Servername to be an array having data elements store as "Server1', 'Server2', 'Server3', etc. based on the sqlquery result. I am planning to utilize $Servername array to loop through each server in future. For now, I am able to successfully connect to database, but I am still not able to get the query result to store in a PowerShell array. Can someone please guide on where I am making mistake?
$SqlAdapter.Fill($DataSet)
This fills the [System.Data.DataSet] instance stored in $DataSet with the query results, which is why you must use $DataSet to get the data you need (untested):
$serverNames = $DataSet.Tables[0].Server
.Tables[0] accesses the first and only [System.Data.DataTable] instance in the dataset containing the query results.
.Server retrieves the the values of the query result's Server column, courtesy of PowerShell's member-access enumeration.
Note that this means that if there's only one result row, $serverNames will contain a single string rather than a single-element array containing that string.
To ensure that an array is always returned, use $serverNames = #($DataSet.Tables[0].Server), or, with a (strong) type constraint, [string[]] $serverNames = $DataSet.Tables[0].Server
As for what you tried:
PowerShell statements only ever need to be separated with ; if they're placed on the same line, which means that all the ; instances in your code are unnecessary.
While $("select distinct Servername from dbo.tableA where Servername like '%hw%'") technically works, there is no reason to wrap a double-quoted string literal ("...") in the subexpression operator - just omit the $(...) enclosure.
As for $Servername = #[SqlAdapter]: perhaps that was just pseudo code, but, to be clear: #[...] isn't a valid syntax construct in PowerShell (at least as of PowerShell 7.3[1]).
[1] By curious coincidence, #[...] just came up as potential future syntax for simplifying PowerShell's [pscustomobject] object-literal syntax - see GitHub issue #18747.

Powershell invoke-sqlcmd Printing The Wrong Output

I have a SQL query that is running as expected however when I try to use it in PowerShell 'Invoke-SqlCmd' module, the output comes out different than when querying the database. I noticed that there are quite a few questions regarding this module but I couldn't find one that is applicable to my case.
Query:
$SQLServer = "localhost"
$query = "SELECT Groups.[Name] AS AGname FROM sys.dm_hadr_availability_group_states States INNER JOIN master.sys.availability_groups Groups ON States.group_id = Groups.group_id WHERE primary_replica = ##Servername"
$HAGName = Invoke-Sqlcmd -query $query -ServerInstance $SQLServer -Database 'database'
if ($HAGName = !$null) {
write-host "Availability group name is $HAGName"
exit 0
}
else {
write-host "Failed to retrieve High Availability group name = [$HAGName]"
exit 1
}
Output in PowerShell: 'Availability group name is True'
Like I mentioned, when querying SQL Server directly I get the correct output. I tried using the 'OutputAs' switch but it didn't help.
Any help will be greatly appreciated.
All the pointers are in the comments on the question, but let me break it down systematically:
!$null is always $true in PowerShell: ! / -not, the logical NOT operator coerces $null to a Boolean, and since [bool] $null is $false, ! $null is $true.
$HAGName = !$null, due to using =, the assignment operator, therefore assigns $true to variable $HAGName.
To instead perform an equality comparison, use -eq, the equality operator.
Therefore, $null -eq $HAGName is what you meant to use (placing the $null on the LHS, for robustness - see the docs).
However, given PowerShell's implicit to-Boolean coercion rules (see the bottom section of this answer), you could simplify to if ($HAGName) { ... } in this case.
Therefore, a more PowerShell-idiomatic reformulation of your code is:
$SQLServer = 'localhost'
$query = 'SELECT Groups.[Name] AS AGname FROM sys.dm_hadr_availability_group_states States INNER JOIN master.sys.availability_groups Groups ON States.group_id = Groups.group_id WHERE primary_replica = ##Servername'
$HAGName = Invoke-Sqlcmd -Query $query -ServerInstance $SQLServer -Database database
if ($HAGName) {
Write-Verbose -Verbose "Availability group name is: "
# Output the System.Data.DataRow instance as-is,
# which also results in proper for-display formatting.
# If you just want the value of the .AGname property (column), use
# $HAGName.AGname instead.
$HAGName
exit 0
}
else {
Write-Warning "Failed to retrieve High Availability group name."
exit 1
}
Note:
The success case implicitly outputs the result, to the success output stream.
Write-Host is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, or redirect it to a file. To output a value, use it by itself; e.g., $value instead of Write-Host $value (or use Write-Output $value, though that is rarely needed); see this answer
I've used a Write-Verbose call (whose output is quiet by default, here I've used -Verbose to force it to show) to provide optional supplemental / status information.
$HAGName now (implicitly) outputs the [System.Data.DataRow] instance returned by the Invoke-SqlCmd call as-is, which also results in proper display formatting - such instances do not stringify meaningfully when used in an expandable (interpolating string); they unhelpfully stringify to their type name, i.e. to verbatim System.Data.DataRow.
However, if you access a specific property (column) of the row, its value may stringify meaningfully, depending on its data type; in your case: `"Availability group name is $($HAGName.AGname)"
To include the usual for-display formatting inside a string - use something like "Availability group name is $($HAGName | Out-String)"

How do I execute a SELECT query against a SQLServer database and iterate results using PowerShell

Say I have a table with 3 columns - "Column1", "Column2", and "Column3" - datatype is varchar(100) for all 3.
Using PowerShell, how do I connect to SQL Server and use SqlDataReader and ForEach operator to view the contents of "Column2"?
Here's roughly how I'm doing it:
$SqlServer = 'sql.example.com';
$SqlDatabase = 'MyDB';
$SqlConnectionString = 'Data Source={0};Initial Catalog={1};Integrated Security=SSPI' -f $SqlServer, $SqlDatabase;
$SqlQuery = "SELECT Name FROM dbo.Person ORDER BY Name;";
$SqlConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList $SqlConnectionString;
$SqlCommand = $SqlConnection.CreateCommand();
$SqlCommand.CommandText = $SqlQuery;
$SqlConnection.Open();
$SqlDataReader = $SqlCommand.ExecuteReader();
#Fetch data and write out to files
while ($SqlDataReader.Read()) {
Write-Output $SqlDataReader['Name'];
}
$SqlConnection.Close();
$SqlConnection.Dispose();
If I remember right, I basically refactored the code from the MSDN example.
For those wondering why I'm using SqlDataReader: Most of my scripts use SqlDataAdapter, but this one retrieves about 8,000 PDFs from a database so I wasn't really interested in calling SqlDataAdapter.Fill(). In exchange for holding shared locks on the table much longer than SqlDataAdapter.Fill() would, SqlDataReader.Read() keeps memory usage down to a manageable level for the client by fetching one record at a time.

Unable to Get Information using PowerShell to Query Oracle

I use PowerShell to query SQL databases, and I am quite familiar with that process. However, I am now tasked with building an automated task that queries Oracle for information.
It seems straight forward: Install proper Oracle DLL's, import them into PS, execute the query much like SQL. However, this is not the case. All I get when I request information is a list called FieldCount. This seems to imply that I am able to see the information, it's just not displaying correctly. I'd like the actual values, and nothing seems to get this for me.
Thanks to anyone who knows anything about this, as my hands are tied and this is the only way I can think of to get this information from Oracle on a scheduled basis. I am not the Oracle admin, I only have read access to this view.
function Get-OracleData($cmdText){
Add-Type -Path 'C:\app\client\username\product\12.1.0\client_1\odp.net\managed\common\Oracle.ManagedDataAccess.dll'
$username = 'username'
$password = 'password'
$con = New-Object Oracle.ManagedDataAccess.Client.OracleConnection('User Id=$username;Password=$password;Data Source=OracleServerName')
$con.Open()
$cmd = New-Object Oracle.ManagedDataAccess.Client.OracleCommand
$cmd.Connection = $con
$cmd.CommandText = $cmdText
$rdr = $cmd.ExecuteReader()
if($rdr.Read()){
return $rdr
}else{return 0}
}
Get-OracleData -cmdText '
SELECT em.employee_number,
em.last_name,
em.first_name,
em.middle_names,
em.email_address,
em.start_date,
em.term_date,
em.location_addr_line_1,
em.location_city,
em.location_work_state,
FROM CustomView em
'
Found the answer in the link below. I was able to get what I needed by inserting the below code at the line where $cmd.CommandText = $cmdText is located in my original post, and getting rid of what's below it.
$ds = New-Object system.Data.DataSet
$da = New-Object Oracle.ManagedDataAccess.Client.OracleDataAdapter($cmd)
[void]$da.fill($ds)
return $ds.Tables[0] | Select *
This returns to a variable, and I can get the first entry using $results[0], and $results[0].EMPLOYEE_NUMBER, etc.
Reference: http://poshcode.org/3965 #line55

Powershell: Retrieve xml data from column in SQL Server database

Following this tutorial I tried to use PowerShell to retrieve xml data from SQL Server, but I only get one element back.
Here is a query to show the actual data:
But running this script I only get one element back:
$SQLServer = 'MYSERVER,1433'
$SQLDBName = "test"
$Query =
#'
USE test
SELECT EventLogXML FROM ForwardedEvents
'#
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $Database; Integrated Security = True"
$SqlConnection.open()
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $Query
$SqlCmd.Connection = $SqlConnection
$xr = $SqlCmd.ExecuteXmlReader()
$xd = New-Object System.Xml.XmlDataDocument
$xd.Load($xr)
$xr.Close()
$SQLConnection.Close()
$xd
$xd only has one element. What am I doing wrong?
---edit
I can confirm its only one xml doc by doing $xd.outerxml which reveals the complete doc. It is only one of the thousand or so event xml docs I'm storing in the EventLogXML column.
I think that XmlDataDocument is mainly for returning a single xml. Basically if you do in sql select * from bla for xml, auto you then can read it with the ExecuteXmlReader and XmlDataDocument. This is not what you want.
Modifying the example you linked to your needs we'll get somethign like:
$con = New-Object System.Data.SqlClient.SqlConnection
$con.ConnectionString = "Server=.; Database=AdventureWorks2012;Integrated Security=true"
$con.open()
$cmd = New-Object System.Data.SqlClient.SqlCommand
$cmd.CommandText = "SELECT Instructions FROM Production.ProductModel WHERE Instructions is not null"
$cmd.Connection = $con
$as = New-Object System.Data.SqlClient.SqlDataAdapter
$ds = New-Object System.Data.DataSet
$as.SelectCommand = $cmd
$as.Fill($ds);
$xmlDocs = $ds.Tables[0] | %{ [xml]$_.Instructions }
Now xmlDocs will contain a list of xml documents, one document per row.
Powershell wraps XML stuff into handy little objects, which you can explore using .Property syntax. If you just look at $xd, powershell by default will only show you the root node.
I don't know the structure of your XML column, but if the root node is called MyRoot, followed by common subnodes called MySub, try something like this:
$xd.MyRoot.MySub
This is just as the linked example shows the need to use $xd.root.Location
Edit
Ok so that is not the problem. Looks like it is by-design to return back only the first row when calling ExecuteXmlReader with a normal select statement (doc here):
if more than one row is returned, the ExecuteXmlReader method attaches
the XmlReader to the value on the first row, and discards the rest of
the result set
From some basic searching around, this blog post seems to explain the issue the best, and provides a workaround. See also here.
I may be out to lunch, but couldn't it be because you are declaring the database as $SQLDBName and then trying to connect to $Database in your connectionstring?

Resources