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)"
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.
We are trying to replace a backup location in a SQL Backup Jobs step (running power shell through several servers)
Below is a PS script i would like to use it:
# $Server is a file with SERVERNAME names
$Jobs = Get-SQLAgentJob -ServerInstance
$Servers Foreach ($job in $Jobs.Where{$_.Name -like 'DatabaseBackup' -and $_.isenabled -eq $true}) {
foreach ($Step in $Job.jobsteps.Where{$_.Name -like 'DatabaseBackup'}) {
$Step.Command = $Step.Command.Replace("Directory = N'C:\Backup\oldname1\oldname2\SERVERNAME'", "Directory = N'C:\Backup2\newname1\newname2\SERVERNAME'")
$Step.Alter()
}
}
It seems like this should work. The only potential problems I see are the following:
named SQL instances: The $servers variable will need to have the servername\instancename format if not using the default instance name
Job and step names: If your job names and job step names are not exactly databasebackup, case excluded, then the -like operator combined with the exact string will not find a match. If the names contain the databasebackup string, you will be safer to use -match "databasebackup" or -like with asterisks on both sides of the string.
Otherwise, this code should just work provided there are not network connectivity or permissions issues.
I am trying to filter some tables from my database which exists in my solution folder. I want to filter all tables that I am pulling from SQL Server:
$existingTables = "Table1", "Table2", "Table3", "Table4"
#getting all tables except existing ones
#SqlQuery = "SELECT name FROM sys.Tables order by name asc"
$filteredTables = ((Invoke-SQL -DataSource $ServerName -DatabaseName $DatabaseName -UserID $UserID -Password $Password -SqlCommand $SQLQuery).name | ? {$_ -notcontains $existingTables})
#$filteredTables returns all tables, including the existing ones...
I've tried $_.name and it is the same result.
You're using the operands of the -notcontains operator in the wrong order. The correct syntax is
reference_array -notcontains item
In your case:
$existingTables -notcontains $_
or
$existingTables -notcontains $_.Name
if you don't expand the property Name.
If you want to use the reference array as the second operand you must use the -notin operator:
$_ -notin $existingTables
However, that operator is not available prior to PowerShell v3.
Alternatively, you could add an exclude clause to your SQL statement, as #vonPryz suggested in the comments. Take care to not open yourself to SQL injection when doing that, though.
Don't do this:
$SQLQuery = #"
SELECT ...
FROM ...
WHERE name NOT IN ('$($existingTables[0])', '$($existingTables[1])', ...)
"#
Use a prepared statement (or "parameterized query" as Microsoft calls them). I don't recognize the cmdlet you're using, though (doesn't seem to be Invoke-Sqlcmd), so I can't tell you how to do it with that cmdlet.
I know this question has already been asked but the answers don't quite fit the constrains i have
So here we go again (sorry for that) :
I have this line in my PowerShell script :
$WITHOUTCLIENT = Invoke-Sqlcmd -h -1 -Database CM_00A -Query "Select Members.Name From CollectionMembers Members Join Collections Coll on Members.SiteID = Coll.SiteID Where Coll.CollectionName = '_SCCM-Machine Sans Client'"
The problem being that although when i count the number of items in this collection, the result is accurate (i.e. $WITHOUTCLIENT.count ) but when i try to display any item in this collection, it keeps on displaying a bloody header (i.e. $WITHOUTCLIENT[0] )
Note that i DON'T want to use an external file to store the result and that i should use Invoke-Sqlcmd, not Sqlcmd
i'm begging for help
Thank you
If you execute query:
$WITHOUTCLIENT = Invoke-Sqlcmd -h -1 -Database CM_00A -Query "Select Members.Name From CollectionMembers Members Join Collections Coll on Members.SiteID = Coll.SiteID Where Coll.CollectionName = '_SCCM-Machine Sans Client'"
You are receiving list of objects. Each object has one property, but it's still an object. To get 'flat' collection, use select with `-ExpandProperty' argument:
$WITHOUTCLIENT = $WITHOUTCLIENT | select -ExpandProperty Name
I need to get Availability Group and Listener name, concatenate both for a list of servers and then use it to get resource cluster.
What I've done so far:
foreach ($cluster in GC "D:\TEST\Servers_List_TEST.txt")
{
$AGName = invoke-sqlcmd -Serverinstance $cluster -query "select left(name,25) as ' ' from sys.availability_groups"
$LNName = invoke-sqlcmd -Serverinstance $cluster -query "select left(dns_name,25) as ' ' from sys.availability_group_listeners"
$NetworkName = "$AGName_$LNName"
Get-ClusterResource -cluster $cluster -name $NetworkName | Get-ClusterParameter HostRecordTTL,RegisterAllProvidersIP }
The main issue is on $NetworkName. It's returning System.data.DataRow, instead of concatenating $AGName_$LNName ( underscore is necessary between both ).
Your main issue is that $AGName and its partner variable $LNName are System.Data.DataRow objects which is what Invoke-SQLcmd returns. They are not just simple strings. Since you are forcing them to strings PowerShell calls the ToString() method of those objects which, in this case, is just the object name.
You have also given that object a property of in your queries (which is just a space). The resulting object is using that property name.
You should add proper column names in your query but you will be able to pull out the relevant data by calling that property either way.
$NetworkName = "$($AGName." ")_$($LNName." ")"
So using subexpressions we have gotten the value of the property [space]