Skip to content

Commit

Permalink
SqlServerDsc: Changes to Invoke-Query (#1407)
Browse files Browse the repository at this point in the history
- Changes to helper function 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).
  - It is possible to redact text in the verbose output by providing
    strings in the new parameter `RedactText`.
  • Loading branch information
johlju authored Jul 22, 2019
1 parent 49ac264 commit adcf2bb
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 14 deletions.
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

0 comments on commit adcf2bb

Please sign in to comment.