From 0e4e0dcff264ffc93f786a682b5c21c4f4f2ec58 Mon Sep 17 00:00:00 2001 From: Matthew Martinez Date: Mon, 24 Jun 2019 05:58:11 +0300 Subject: [PATCH 1/5] Updated invoke-query to allow smo object to be passed --- CHANGELOG.md | 9 ++ .../SqlServerDsc.Common.psm1 | 94 ++++++++++++++----- 2 files changed, 80 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87bd185e4..c160f62b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,15 @@ - Fix issue where calling Get would return an error because the database name list may have been returned as a string instead of as a string array ([issue #1368](https://github.com/PowerShell/SqlServerDsc/issues/1368)). +- Changes to common module: Invoke-Query + - Fixes issues in [issue #1355](https://github.com/PowerShell/SqlServerDsc/issues/1355) + - Works together with Connect-SQL now + - Parameters and Aliases now match that of Connect-SQL + - Can now pass in credentials + - Can now pass in 'Microsoft.SqlServer.Management.Smo.Server' object + - Can also pipe in 'Microsoft.SqlServer.Management.Smo.Server' object + - Can pipe Connect-SQL | Invoke-Query + - Added default vaules to Invoke-Query ## 12.5.0.0 diff --git a/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 b/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 index f3e8b5001..549609e4e 100644 --- a/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 +++ b/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 @@ -1569,47 +1569,66 @@ function Restart-ReportingServicesService } } + <# .SYNOPSIS - Executes a query on the specified database. + Executes a query on the specified database. .PARAMETER SQLServer - The hostname of the server that hosts the SQL instance. + The hostname of the server that hosts the SQL instance. .PARAMETER SQLInstanceName - The name of the SQL instance that hosts the database. + The name of the SQL instance that hosts the database. .PARAMETER Database - Specify the name of the database to execute the query on. + Specify the name of the database to execute the query on. .PARAMETER Query - The query string to execute. + 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. + + .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. .PARAMETER WithResults - Specifies if the query should return results. + Specifies if the query should return results. - .PARAMETER StatementTimeout - Set the query StatementTimeout in seconds. Default 600 seconds (10mins). + .EXAMPLE + Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER -Database master ` + -Query 'SELECT name FROM sys.databases' -WithResults .EXAMPLE - Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER -Database master -Query 'SELECT name FROM sys.databases' -WithResults + Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER -Database master ` + -Query 'RESTORE DATABASE [NorthWinds] WITH RECOVERY' .EXAMPLE - Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER -Database master -Query 'RESTORE DATABASE [NorthWinds] WITH RECOVERY' + Connect-SQL @sqlConnectionParameters | Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER ` + -Database master -Query 'SELECT name FROM sys.databases' -WithResults #> function Invoke-Query { - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName='SqlServer')] param ( - [Parameter(Mandatory = $true)] + [Alias("ServerName")] + [Parameter(ParameterSetName='SqlServer')] [ValidateNotNullOrEmpty()] [System.String] - $SQLServer, + $SQLServer = $env:COMPUTERNAME, - [Parameter(Mandatory = $true)] + [Alias("InstanceName")] + [Parameter(ParameterSetName='SqlServer')] [System.String] - $SQLInstanceName, + $SQLInstanceName = 'MSSQLSERVER', [Parameter(Mandatory = $true)] [System.String] @@ -1619,23 +1638,52 @@ function Invoke-Query [System.String] $Query, + [Alias("SetupCredential")] [Parameter()] - [Switch] - $WithResults, + [System.Management.Automation.PSCredential] + $DatabaseCredential, [Parameter()] + [ValidateSet('Integrated', 'WindowsUser', 'SqlLogin')] + [System.String] + $LoginType = 'Integrated', + + [Parameter(ValueFromPipeline, ParameterSetName='SqlObject', Mandatory = $true)] [ValidateNotNull()] - [System.Int32] - $StatementTimeout = 600 + [Microsoft.SqlServer.Management.Smo.Server] + $SqlServerObject, + + [Parameter()] + [Switch] + $WithResults ) - $serverObject = Connect-SQL -ServerName $SQLServer -InstanceName $SQLInstanceName -StatementTimeout $StatementTimeout + # If we don't have an smo object, then we try to use credentials + if ($PSCmdlet.ParameterSetName -eq 'SqlObject') + { + $smoConnectObject = $SqlServerObject + } + elseif ($PSCmdlet.ParameterSetName -eq 'SqlServer') + { + $connectSQLParamaters = @{ + ServerName = $SQLServer + InstanceName = $SQLInstanceName + LoginType = $LoginType + } + + if ($PSBoundParameters.ContainsKey('DatabaseCredential')) + { + $connectSQLParamaters.SetupCredential = $DatabaseCredential + } + + $smoConnectObject = Connect-SQL @connectSQLParamaters + } - if ( $WithResults ) + if ($WithResults) { try { - $result = $serverObject.Databases[$Database].ExecuteWithResults($Query) + $result = $smoConnectObject.Databases[$Database].ExecuteWithResults($Query) } catch { @@ -1647,7 +1695,7 @@ function Invoke-Query { try { - $serverObject.Databases[$Database].ExecuteNonQuery($Query) + $smoConnectObject.Databases[$Database].ExecuteNonQuery($Query) } catch { From cc237dbd562dc08428dabe06de7c85adc5d08a94 Mon Sep 17 00:00:00 2001 From: Matthew Martinez Date: Mon, 24 Jun 2019 06:04:06 +0300 Subject: [PATCH 2/5] added test cases for invoke-query and cleaned up --- Tests/Unit/SqlServerDsc.Common.Tests.ps1 | 177 +++++++++++++++++------ 1 file changed, 132 insertions(+), 45 deletions(-) diff --git a/Tests/Unit/SqlServerDsc.Common.Tests.ps1 b/Tests/Unit/SqlServerDsc.Common.Tests.ps1 index 9da6ad020..d336d3909 100644 --- a/Tests/Unit/SqlServerDsc.Common.Tests.ps1 +++ b/Tests/Unit/SqlServerDsc.Common.Tests.ps1 @@ -1390,46 +1390,55 @@ InModuleScope 'SqlServerDsc.Common' { BeforeAll { $mockExpectedQuery = '' - $mockConnectSql = { - return @( - ( - New-Object -TypeName PSObject -Property @{ - Databases = @{ - 'master' = ( - New-Object -TypeName PSObject -Property @{ Name = 'master' } | - Add-Member -MemberType ScriptMethod -Name ExecuteNonQuery -Value { - param - ( - [Parameter()] - [System.String] - $sqlCommand - ) - - if ( $sqlCommand -ne $mockExpectedQuery ) - { - throw - } - } -PassThru | - Add-Member -MemberType ScriptMethod -Name ExecuteWithResults -Value { - param - ( - [Parameter()] - [System.String] - $sqlCommand - ) - - if ( $sqlCommand -ne $mockExpectedQuery ) - { - throw - } - - return New-Object -TypeName System.Data.DataSet - } -PassThru - ) - } - } - ) + $mockSetupCredentialUserName = 'TestUserName12345' + $mockSetupCredentialPassword = 'StrongOne7.' + $mockSetupCredentialSecurePassword = ConvertTo-SecureString -String $mockSetupCredentialPassword -AsPlainText -Force + $mockSetupCredential = New-Object -TypeName PSCredential -ArgumentList ($mockSetupCredentialUserName, $mockSetupCredentialSecurePassword) + + $masterDatabaseObject = New-Object -TypeName PSObject + $masterDatabaseObject | Add-Member -MemberType NoteProperty -Name 'Name' -Value 'master' + $masterDatabaseObject | Add-Member -MemberType ScriptMethod -Name 'ExecuteNonQuery' -Value { + param + ( + [Parameter()] + [System.String] + $sqlCommand ) + + if ( $sqlCommand -ne $mockExpectedQuery ) + { + throw + } + } + + $masterDatabaseObject | Add-Member -MemberType ScriptMethod -Name 'ExecuteWithResults' -Value { + param + ( + [Parameter()] + [System.String] + $sqlCommand + ) + + if ( $sqlCommand -ne $mockExpectedQuery ) + { + throw + } + + return New-Object -TypeName System.Data.DataSet + } + + $databasesObject = New-Object -TypeName PSObject + $databasesObject | Add-Member -MemberType NoteProperty -Name 'Databases' -Value @{ + 'master' = $masterDatabaseObject + } + + $mockSMOServer = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' + $mockSMOServer | Add-Member -MemberType NoteProperty -Name 'Databases' -Value @{ + 'master' = $masterDatabaseObject + } -Force + + $mockConnectSql = { + return @($databasesObject) } $mockThrowLocalizedMessage = { @@ -1443,10 +1452,17 @@ InModuleScope 'SqlServerDsc.Common' { } $queryParams = @{ - SQLServer = 'Server1' - SQLInstanceName = 'MSSQLSERVER' - Database = 'master' - Query = '' + SQLServer = 'Server1' + SQLInstanceName = 'MSSQLSERVER' + Database = 'master' + Query = '' + DatabaseCredential = $mockSetupCredential + } + + $queryParametersWithSMO = @{ + Query = '' + SqlServerObject = $mockSMOServer + Database = 'master' } Context 'Execute a query with no results' { @@ -1462,7 +1478,9 @@ InModuleScope 'SqlServerDsc.Common' { It 'Should throw the correct error, ExecuteNonQueryFailed, when executing the query fails' { $queryParams.Query = 'BadQuery' - { Invoke-Query @queryParams } | Should -Throw ($script:localizedData.ExecuteNonQueryFailed -f $queryParams.Database) + { Invoke-Query @queryParams } | Should -Throw ( + $script:localizedData.ExecuteNonQueryFailed -f $queryParams.Database + ) Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly } @@ -1481,11 +1499,80 @@ InModuleScope 'SqlServerDsc.Common' { It 'Should throw the correct error, ExecuteQueryWithResultsFailed, when executing the query fails' { $queryParams.Query = 'BadQuery' - { Invoke-Query @queryParams -WithResults } | Should -Throw ($script:localizedData.ExecuteQueryWithResultsFailed -f $queryParams.Database) + { Invoke-Query @queryParams -WithResults } | Should -Throw ( + $script:localizedData.ExecuteQueryWithResultsFailed -f $queryParams.Database + ) Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly } } + + Context 'Pass 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'" + $mockExpectedQuery = $queryParametersWithSMO.Query.Clone() + + { Invoke-Query @queryParametersWithSMO } | Should -Not -Throw + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error, ExecuteNonQueryFailed, when executing the query fails' { + $queryParametersWithSMO.Query = 'BadQuery' + + { Invoke-Query @queryParametersWithSMO } | Should -Throw ( + $script:localizedData.ExecuteNonQueryFailed -f $queryParams.Database + ) + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 0 -Exactly + } + } + + Context 'Execute a query with piped SMO server object' { + It 'Should execute the query and return a result set' { + $queryParametersWithSMO.Query = 'SELECT name FROM sys.databases' + $mockExpectedQuery = $queryParametersWithSMO.Query.Clone() + + Invoke-Query @queryParametersWithSMO -WithResults | Should -Not -BeNullOrEmpty + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error, ExecuteQueryWithResultsFailed, when executing the query fails' { + $queryParametersWithSMO.Query = 'BadQuery' + + { Invoke-Query @queryParametersWithSMO -WithResults } | Should -Throw ( + $script:localizedData.ExecuteQueryWithResultsFailed -f $queryParams.Database + ) + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 0 -Exactly + } + } + + Context 'Execute a query with results' { + It 'Should execute the query and return a result set' { + $mockQuery = 'SELECT name FROM sys.databases' + $mockExpectedQuery = $mockQuery + + $mockSMOServer | Invoke-Query -Query $mockQuery -Database master -WithResults | + Should -Not -BeNullOrEmpty + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error, ExecuteQueryWithResultsFailed, when executing the query fails' { + $mockQuery = 'BadQuery' + + { $mockSMOServer | Invoke-Query -Query $mockQuery -Database master -WithResults } | + Should -Throw ( + $script:localizedData.ExecuteQueryWithResultsFailed -f $queryParams.Database + ) + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 0 -Exactly + } + } + } } Describe 'SqlServerDsc.Common\Update-AvailabilityGroupReplica' -Tag 'UpdateAvailabilityGroupReplica' { From a6f03e76c084ee0bfb7f0e264bddb2322450cbe0 Mon Sep 17 00:00:00 2001 From: Matthew Martinez Date: Mon, 24 Jun 2019 07:54:05 +0300 Subject: [PATCH 3/5] Added timeout back to invoke-query --- .../SqlServerDsc.Common/SqlServerDsc.Common.psm1 | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 b/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 index 549609e4e..6b18c0e6b 100644 --- a/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 +++ b/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 @@ -1602,6 +1602,9 @@ function Restart-ReportingServicesService .PARAMETER WithResults Specifies if the query should return results. + .PARAMETER StatementTimeout + Set the query StatementTimeout in seconds. Default 600 seconds (10mins). + .EXAMPLE Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER -Database master ` -Query 'SELECT name FROM sys.databases' -WithResults @@ -1655,7 +1658,11 @@ function Invoke-Query [Parameter()] [Switch] - $WithResults + $WithResults, + + [ValidateNotNull()] + [System.Int32] + $StatementTimeout = 600 ) # If we don't have an smo object, then we try to use credentials @@ -1666,9 +1673,10 @@ function Invoke-Query elseif ($PSCmdlet.ParameterSetName -eq 'SqlServer') { $connectSQLParamaters = @{ - ServerName = $SQLServer - InstanceName = $SQLInstanceName - LoginType = $LoginType + ServerName = $SQLServer + InstanceName = $SQLInstanceName + LoginType = $LoginType + StatementTimeout = $StatementTimeout } if ($PSBoundParameters.ContainsKey('DatabaseCredential')) From 6dce92c7000a959f61324f2f8bde5583fdd613c2 Mon Sep 17 00:00:00 2001 From: Matthew Martinez Date: Thu, 27 Jun 2019 08:28:24 +0300 Subject: [PATCH 4/5] Fixed review issues --- .../SqlServerDsc.Common.psm1 | 17 ++++++++--------- Tests/Unit/SqlServerDsc.Common.Tests.ps1 | 4 ++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 b/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 index 6b18c0e6b..f780af7b6 100644 --- a/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 +++ b/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 @@ -1614,8 +1614,8 @@ function Restart-ReportingServicesService -Query 'RESTORE DATABASE [NorthWinds] WITH RECOVERY' .EXAMPLE - Connect-SQL @sqlConnectionParameters | Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER ` - -Database master -Query 'SELECT name FROM sys.databases' -WithResults + Connect-SQL @sqlConnectionParameters | Invoke-Query -Database master ` + -Query 'SELECT name FROM sys.databases' -WithResults #> function Invoke-Query { @@ -1665,14 +1665,13 @@ function Invoke-Query $StatementTimeout = 600 ) - # If we don't have an smo object, then we try to use credentials if ($PSCmdlet.ParameterSetName -eq 'SqlObject') { - $smoConnectObject = $SqlServerObject + $serverObject = $SqlServerObject } elseif ($PSCmdlet.ParameterSetName -eq 'SqlServer') { - $connectSQLParamaters = @{ + $connectSQLParameters = @{ ServerName = $SQLServer InstanceName = $SQLInstanceName LoginType = $LoginType @@ -1681,17 +1680,17 @@ function Invoke-Query if ($PSBoundParameters.ContainsKey('DatabaseCredential')) { - $connectSQLParamaters.SetupCredential = $DatabaseCredential + $connectSQLParameters.SetupCredential = $DatabaseCredential } - $smoConnectObject = Connect-SQL @connectSQLParamaters + $serverObject = Connect-SQL @connectSQLParameters } if ($WithResults) { try { - $result = $smoConnectObject.Databases[$Database].ExecuteWithResults($Query) + $result = $serverObject.Databases[$Database].ExecuteWithResults($Query) } catch { @@ -1703,7 +1702,7 @@ function Invoke-Query { try { - $smoConnectObject.Databases[$Database].ExecuteNonQuery($Query) + $serverObject.Databases[$Database].ExecuteNonQuery($Query) } catch { diff --git a/Tests/Unit/SqlServerDsc.Common.Tests.ps1 b/Tests/Unit/SqlServerDsc.Common.Tests.ps1 index d336d3909..3ca5efd7a 100644 --- a/Tests/Unit/SqlServerDsc.Common.Tests.ps1 +++ b/Tests/Unit/SqlServerDsc.Common.Tests.ps1 @@ -1529,7 +1529,7 @@ InModuleScope 'SqlServerDsc.Common' { } } - Context 'Execute a query with piped SMO server object' { + Context 'Execute 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() @@ -1550,7 +1550,7 @@ InModuleScope 'SqlServerDsc.Common' { } } - Context 'Execute a query with results' { + Context 'Execute 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 From f12e9c58dd028f1ce8c468304466b901be180ac8 Mon Sep 17 00:00:00 2001 From: Matthew Martinez Date: Thu, 27 Jun 2019 09:30:56 +0300 Subject: [PATCH 5/5] fixed changelog for new release --- CHANGELOG.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32c889f84..446e47e43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +- Changes to common module: Invoke-Query + - Fixes issues in [issue #1355](https://github.com/PowerShell/SqlServerDsc/issues/1355) + - Works together with Connect-SQL now + - Parameters and Aliases now match that of Connect-SQL + - Can now pass in credentials + - Can now pass in 'Microsoft.SqlServer.Management.Smo.Server' object + - Can also pipe in 'Microsoft.SqlServer.Management.Smo.Server' object + - Can pipe Connect-SQL | Invoke-Query + - Added default vaules to Invoke-Query + ## 13.0.0.0 - Changes to SqlServerDsc @@ -76,15 +86,6 @@ - Fix issue where calling Get would return an error because the database name list may have been returned as a string instead of as a string array ([issue #1368](https://github.com/PowerShell/SqlServerDsc/issues/1368)). -- Changes to common module: Invoke-Query - - Fixes issues in [issue #1355](https://github.com/PowerShell/SqlServerDsc/issues/1355) - - Works together with Connect-SQL now - - Parameters and Aliases now match that of Connect-SQL - - Can now pass in credentials - - Can now pass in 'Microsoft.SqlServer.Management.Smo.Server' object - - Can also pipe in 'Microsoft.SqlServer.Management.Smo.Server' object - - Can pipe Connect-SQL | Invoke-Query - - Added default vaules to Invoke-Query ## 12.5.0.0