0

This has probably been answered somewhere before, but I can't figure out how to ask google the right question. To be able to define fewer variables, I would like to be able to pass a member of a variable or array to a SQL query. As an example, I would like to be able to define something like $date = get-date and use $date.month to just pass just the month in the query.

The issues I run into is that the period used to define the member seems to break things in a SQL query. Is there a way to properly punctuate this type of variable in this situation?

For fuller context:

$ConnectionString = "Server=" + $TargetServer + ";Database=" + $TargetDatabase + ";Trusted_Connection=$true;";
$TargetConnection = New-Object System.Data.SqlClient.SqlConnection($ConnectionString);
$TargetConnection.Open();

$date = get-date

$sql = "SELECT *
  FROM [database].[dbo].[table]
  where ([MONTH] = $date.month and [YEAR] = $date.year)"

$TargetCommand = New-Object System.Data.SqlClient.SqlCommand($sql, $TargetConnection);
$TargetCommand.ExecuteScalar()
$TargetConnection.Close()

The month and year columns have the values stored as int.

mouseskowitz
  • 45
  • 2
  • 9
  • 2
    What you have above isn't parametrisation, it's injection. – Thom A Jun 07 '22 at 08:19
  • 2
    You need to use subexpressions for the interpolation to work correctly: `$($date.month)` and `$($date.year)` – boxdog Jun 07 '22 at 08:43
  • 1
    Larnu's answer is a safer alternative to your approach, but, as boxdog points out, the only problem with your approach is a syntax problem in your string-interpolation syntax: In short: In order to embed _expressions_ in an expandable string (`"..."`), you must enclose them in `$(...)`. Notably, this includes property and indexed access (e.g., `$($var.property)`, `$($var[0])`). Only variables _as a whole_ do not require this (e.g., `$var`, `$env:USERNAME`). See the [linked duplicate](https://stackoverflow.com/q/1145704/45375). – mklement0 Jun 07 '22 at 12:32

1 Answers1

5

As I mentioned, what you have above injection, not parametrisation. You don't tell us why what you have isn't working, so this is some what of a guess, but most likely you want something like this:

$connectionString = 'Server=' + $TargetServer + ';Database=' + $TargetDatabase + ';Trusted_Connection=$true;'
$sqlConn = New-Object System.Data.SqlClient.SqlConnection($ConnectionString)
#Start a try, so that we can close the connection on error
try{
    $sqlConn.Open()
    $sqlCmd = $sqlConn.CreateCommand()
    $sqlCmd.Connection = $sqlConn #Assign the connection to the command
    $query = "SELECT *
    FROM [dbo].[table] --Database isn't needed, you have defined it in your connection string
    WHERE [MONTH] = @Month and [YEAR] = @Year;" #Define the query
    $sqlCmd.CommandText = $query
    $date = get-date #Get the current date
    $sqlCmd.Parameters.Add("@Month", 8).Value = $date.month #Add Month Parameter
    $sqlCmd.Parameters.Add("@Year", [System.Data.SqlDbType]::Int).Value = $date.year #Add year Parameter
    #I don't know what you want to do with the data, so I put it into a data adapter
    $sqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter $sqlcmd
    $dataSet = New-Object System.Data.DataSet
    $sqlAdapter.Fill($dataSet)
}
finally{
    if ($sqlAdapter -ne $null) { $sqlAdapter.Dispose(); }
    if ($sqlConn -ne $null) { $sqlConn.Close(); }
}

$dataSet.Tables

You can get the list of the enum values for the parameter types in the documentation; note I use 8 in the above for int. AlwaysLearninghas since reminded me of the correct syntax, so I now demonstrate both. @Month passes uses an enum value, an @Year uses the name of the datatype from sqlDbType.

From the comments it seems like you want to inject; in some ways this defeats of the object of using the .Net objects. If you simply want to inject the data, the you could just use Invoke-SqlCmd:

Invoke-Sqlcmd -Query "SELECT * FROM dbo.[table] WHERE Month = $($(get-date).month) AND [Year] = $($(get-date).year);" -ServerInstance $TargetServer -Database $TargetDatabase
Charlieface
  • 52,284
  • 6
  • 19
  • 43
Thom A
  • 88,727
  • 11
  • 45
  • 75
  • 1
    Instead of magic numbers you can specify `[System.Data.SqlDbType]::Int` – AlwaysLearning Jun 07 '22 at 08:55
  • 1
    Thanks, @AlwaysLearning . I tried without the brackets and it wasn't working; been some time since I've .Net within Powershell. – Thom A Jun 07 '22 at 08:55
  • @Larnu thank you for your detailed explanation. While it solves the issue technically it adds more complexity than I was looking for. I also don't understand it well enough to know if it solves all the situations I've run into. boxdog gave me what I was looking for in the comments of the initial post. – mouseskowitz Jun 07 '22 at 09:11
  • The thing is, if you're going down the injection route, you might as well not use the .Net objects, @mouseskowitz . If you just want to inject, why not just use `invoke-sqlcmd`? – Thom A Jun 07 '22 at 09:15
  • I've added an example for injection with `Invoke-SqlCmd`, @mouseskowitz , but I would suggest that if you want to actually parametrise your queries, then use the .Net objects and `Parameters.Add`. – Thom A Jun 07 '22 at 09:26
  • @Larnu I'm taking some code that one of our DBA admins gave me and twisting it to do what I need. I have zero programing background. While what you're talking about is probably best practice, it's going over my head. – mouseskowitz Jun 07 '22 at 09:28
  • If you're taking someone else's code, then you should be taking the time to understand it, @mouseskowitz . If you've taken your DBA's code and don't understand it, talk to them about it. – Thom A Jun 07 '22 at 09:29
  • Completely pendantic, but you only need one pair of `$()`, it's a subexpression so gets evaluated before being returned. – Mike Anthony Jun 07 '22 at 11:12
  • Honestly, I just wouldn't want to go down the injection route anyway, @MikeAnthony ; it's only there because the OP said that parametrisation was "too complicated", which seemed odd when they had already chosen to go down the .Net route. But yes, `$($(get-date).month)` could be replaced with `$((get-date).month)`. – Thom A Jun 07 '22 at 11:16