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.
Related
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.
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.
I am tasked with pulling information from SAP and cross referencing it to information pulled from Active Directory. The first step I need to obviously accomplish would be to figure out how to pull certain information out of the SQL Server. Here is what I have so far (thanks to several users here who have answered somewhat similar questions):
$SQLServer = "WEB-PRDSQ"
$SQLDBName = "PRD"
$SqlQuery = "SELECT * from prd.ZEMPLOYEE;"
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $SQLDBName; User ID = DELETED; Password = DELETED;"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
$DataSet.Tables[0] | out-file "C:\Scripts\SQL\SQLData.csv"
And this is the output it gives me: http://i.imgur.com/R6Y5HU2.png
For one, this is an ugly layout that is hard to read when there are 145,361 lines of text. And 2, I don't need all this information. All I need is their sAMAccountName (I don't see this as one of the output, but that's what it's called in AD), Emp_ID, Status, Hire_Date, Location, Emp_Title, LastLogonDate (once again, don't see this as an output but I know it's in AD), and lastly Term_Date.
When I try to change the "SELECT * from prd.ZEMPLOYEE" and change out the * for Status, Hire_Date, etc, it gives the error "Invalid column name 'Status'" (or whatever column I have listed first).
Is there someone out there who is patient enough to help work me through this and help me create this? I've only taken one Database class so I kinda know what I am talking about, but also don't know the intricate details that this may require. I am willing to help provide any information I need to.
Per earlier conversation...
Database Collation is case sensitive and column names must match the same case shown in the results file from the Select * From... query.
Example:
Select EMP_ID, STATUS From prd.ZEMPLOYEE
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
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?