Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SqlServerDsc: Changes to Invoke-Query #1407

Merged
merged 2 commits into from
Jul 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
- Can also pipe in 'Microsoft.SqlServer.Management.Smo.Server' object.
- Can pipe Connect-SQL | Invoke-Query.
- Added default values to Invoke-Query.
- Now it will output verbose messages of the query that is run, so it
not as quiet of what it is doing when a user asks for verbose output
([issue #1404](https://github.com/PowerShell/SqlServerDsc/issues/1404)).
- It is possible to redact text in the verbose output by providing
strings in the new parameter `RedactText`.
- Minor style fixes in unit tests.
- Changes to helper function Connect-SQL
- When impersonating WindowsUser credential use the NetworkCredential UserName.
Expand Down
47 changes: 42 additions & 5 deletions Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -1594,24 +1594,30 @@ function Restart-ReportingServicesService
The query string to execute.

.PARAMETER DatabaseCredential
PSCredential object with the credentials to use to impersonate a user when connecting.
If this is not provided then the current user will be used to connect to the SQL Server Database Engine instance.
PSCredential object with the credentials to use to impersonate a user
when connecting. If this is not provided then the current user will be
used to connect to the SQL Server Database Engine instance.

.PARAMETER LoginType
Specifies which type of logon credential should be used. The valid types are
Integrated, WindowsUser, and SqlLogin. If WindowsUser or SqlLogin are specified
then the SetupCredential needs to be specified as well.

.PARAMETER SqlServerObject
You can pass in an object type of 'Microsoft.SqlServer.Management.Smo.Server'. This can also be passed in
through the pipeline allowing you to use connect-sql | invoke-query if you wish.
You can pass in an object type of 'Microsoft.SqlServer.Management.Smo.Server'.
This can also be passed in through the pipeline. See examples.

.PARAMETER WithResults
Specifies if the query should return results.

.PARAMETER StatementTimeout
Set the query StatementTimeout in seconds. Default 600 seconds (10mins).

.PARAMETER RedactText
One or more strings to redact from the query when verbose messages are
written to the console. Strings here will be escaped so they will not
be interpreted as regular expressions (RegEx).

.EXAMPLE
Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER -Database master `
-Query 'SELECT name FROM sys.databases' -WithResults
Expand All @@ -1623,6 +1629,12 @@ function Restart-ReportingServicesService
.EXAMPLE
Connect-SQL @sqlConnectionParameters | Invoke-Query -Database master `
-Query 'SELECT name FROM sys.databases' -WithResults

.EXAMPLE
Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER -Database MyDatabase `
-Query "select * from MyTable where password = 'Pa\ssw0rd1' and password = 'secret passphrase'" `
-WithResults -RedactText @('Pa\sSw0rd1','Secret PassPhrase') -Verbose

#>
function Invoke-Query
{
Expand Down Expand Up @@ -1670,7 +1682,11 @@ function Invoke-Query
[Parameter()]
[ValidateNotNull()]
[System.Int32]
$StatementTimeout = 600
$StatementTimeout = 600,

[Parameter()]
[System.String[]]
$RedactText
)

if ($PSCmdlet.ParameterSetName -eq 'SqlObject')
Expand All @@ -1694,10 +1710,27 @@ function Invoke-Query
$serverObject = Connect-SQL @connectSQLParameters
}

$redactedQuery = $Query

foreach ($redactString in $RedactText)
{
<#
Escaping the string to handle strings which could look like
regular expressions, like passwords.
#>
$escapedRedactedString = [System.Text.RegularExpressions.Regex]::Escape($redactString)

$redactedQuery = $redactedQuery -ireplace $escapedRedactedString,'*******'
}

if ($WithResults)
{
try
{
Write-Verbose -Message (
$script:localizedData.ExecuteQueryWithResults -f $redactedQuery
) -Verbose

$result = $serverObject.Databases[$Database].ExecuteWithResults($Query)
}
catch
Expand All @@ -1710,6 +1743,10 @@ function Invoke-Query
{
try
{
Write-Verbose -Message (
$script:localizedData.ExecuteNonQuery -f $redactedQuery
) -Verbose

$serverObject.Databases[$Database].ExecuteNonQuery($Query)
}
catch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,6 @@ ConvertFrom-StringData @'
ConnectingUsingIntegrated = Connecting as current user '{0}' using integrated security. (SQLCOMMON0054)
CredentialsNotSpecified = The Logon type of '{0}' was specified which requires credentials, but the credentials parameter was not specified. (SQLCOMMON0055)
ConnectingUsingImpersonation = Impersonate credential '{0}' with login type '{1}'. (SQLCOMMON0056)
ExecuteQueryWithResults = Returning the results of the query `{0}`. (SQLCOMMON0057)
ExecuteNonQuery = Executing the query `{0}`. (SQLCOMMON0058)
'@
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,7 @@ ConvertFrom-StringData @'
ClusterLoginPermissionsPresent = The cluster login '{0}' has the required permissions. (SQLCOMMON0053)
ConnectingUsingIntegrated = Anslutning som nuvarande användare '{0}' med integrerad säkerhet. (SQLCOMMON0054)
CredentialsNotSpecified = The Logon type of '{0}' was specified which requires credentials, but the credentials parameter was not specified. (SQLCOMMON0055)
ConnectingUsingImpersonation = Impersoner credential '{0}' med inloggningstyp '{1}'. (SQLCOMMON0056)
ConnectingUsingImpersonation = Uppträder som behörigheten '{0}' med inloggningstyp '{1}'. (SQLCOMMON0056)
ExecuteQueryWithResults = Returnerar resultatet av frågan `{0}`. (SQLCOMMON0057)
ExecuteNonQuery = Exekverar frågan `{0}`. (SQLCOMMON0058)
'@
70 changes: 62 additions & 8 deletions Tests/Unit/SqlServerDsc.Common.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1465,14 +1465,16 @@ InModuleScope 'SqlServerDsc.Common' {
Database = 'master'
}

Context 'Execute a query with no results' {
Context 'When executing a query with no results' {
AfterEach {
Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly
}

It 'Should execute the query silently' {
$queryParams.Query = "EXEC sp_configure 'show advanced option', '1'"
$mockExpectedQuery = $queryParams.Query.Clone()

{ Invoke-Query @queryParams } | Should -Not -Throw

Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly
}

It 'Should throw the correct error, ExecuteNonQueryFailed, when executing the query fails' {
Expand All @@ -1481,12 +1483,37 @@ InModuleScope 'SqlServerDsc.Common' {
{ Invoke-Query @queryParams } | Should -Throw (
$script:localizedData.ExecuteNonQueryFailed -f $queryParams.Database
)
}

Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly
Context 'When text should be redacted' {
BeforeAll {
Mock -CommandName Write-Verbose -ParameterFilter {
$Message -eq (
$script:localizedData.ExecuteNonQuery -f
"select * from MyTable where password = '*******' and password = '*******'"
)
} -MockWith {
<#
MUST return another message than the parameter filter
is looking for, otherwise we get into a endless loop.
We returning the to show in the output how the verbose
message was redacted.
#>
Write-Verbose -Message ('MOCK OUTPUT: {0}' -f $Message) -Verbose
}
}

It 'Should execute the query silently and redact text in the verbose output' {
$queryParams.Query = "select * from MyTable where password = 'Pa\ssw0rd1' and password = 'secret passphrase'"
$mockExpectedQuery = $queryParams.Query.Clone()

# The `Secret PassPhrase` is using the casing like this to test case-insensitive replace.
{ Invoke-Query @queryParams -RedactText @('Pa\sSw0rd1','Secret PassPhrase') } | Should -Not -Throw
}
}
}

Context 'Execute a query with results' {
Context 'When executing a query with results' {
It 'Should execute the query and return a result set' {
$queryParams.Query = 'SELECT name FROM sys.databases'
$mockExpectedQuery = $queryParams.Query.Clone()
Expand All @@ -1505,9 +1532,36 @@ InModuleScope 'SqlServerDsc.Common' {

Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly
}

Context 'When text should be redacted' {
BeforeAll {
Mock -CommandName Write-Verbose -ParameterFilter {
$Message -eq (
$script:localizedData.ExecuteQueryWithResults -f
"select * from MyTable where password = '*******' and password = '*******'"
)
} -MockWith {
<#
MUST return another message than the parameter filter
is looking for, otherwise we get into a endless loop.
We returning the to show in the output how the verbose
message was redacted.
#>
Write-Verbose -Message ('MOCK OUTPUT: {0}' -f $Message) -Verbose
}
}

It 'Should execute the query silently and redact text in the verbose output' {
$queryParams.Query = "select * from MyTable where password = 'Pa\ssw0rd1' and password = 'secret passphrase'"
$mockExpectedQuery = $queryParams.Query.Clone()

# The `Secret PassPhrase` is using the casing like this to test case-insensitive replace.
{ Invoke-Query @queryParams -RedactText @('Pa\sSw0rd1','Secret PassPhrase') -WithResults } | Should -Not -Throw
}
}
}

Context 'Pass in an SMO Server Object' {
Context 'When passing in an SMO Server Object' {
Context 'Execute a query with no results' {
It 'Should execute the query silently' {
$queryParametersWithSMO.Query = "EXEC sp_configure 'show advanced option', '1'"
Expand All @@ -1529,7 +1583,7 @@ InModuleScope 'SqlServerDsc.Common' {
}
}

Context 'Execute a query with results' {
Context 'When executing a query with results' {
It 'Should execute the query and return a result set' {
$queryParametersWithSMO.Query = 'SELECT name FROM sys.databases'
$mockExpectedQuery = $queryParametersWithSMO.Query.Clone()
Expand All @@ -1550,7 +1604,7 @@ InModuleScope 'SqlServerDsc.Common' {
}
}

Context 'Execute a query with piped SMO server object' {
Context 'When executing a query with piped SMO server object' {
It 'Should execute the query and return a result set' {
$mockQuery = 'SELECT name FROM sys.databases'
$mockExpectedQuery = $mockQuery
Expand Down