diff --git a/CHANGELOG.md b/CHANGELOG.md index 33bb9da3a..86fedd4fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -214,6 +214,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 for SQL Server Reporting Services or Power BI Report Server. Supports waiting for dependent services, configurable wait time, and accepts pipeline input from `Get-SqlDscRSConfiguration`. +- Added public commands `Get-SqlDscRSSslCertificateBinding`, + `Add-SqlDscRSSslCertificateBinding`, `Remove-SqlDscRSSslCertificateBinding`, + and `Set-SqlDscRSSslCertificateBinding` to manage SSL certificate bindings + for SQL Server Reporting Services or Power BI Report Server. These commands + wrap the `ListSSLCertificateBindings`, `CreateSSLCertificateBinding`, and + `RemoveSSLCertificateBinding` CIM methods. The `Set-SqlDscRSSslCertificateBinding` + command provides a declarative approach to set SSL bindings to an exact list. - Added public command `New-SqlDscRSEncryptionKey` to delete and regenerate the Reporting Services encryption key. Wraps the `DeleteEncryptionKey` CIM method. Warning: This operation cannot be undone and renders all encrypted content @@ -237,6 +244,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 by calling the `InitializeReportServer` CIM method. Used to complete initial configuration after database and URL setup ([issue #2014](https://github.com/dsccommunity/SqlServerDsc/issues/2014)). +- Added public command `Get-SqlDscRSSslCertificate` to list available SSL + certificates that can be used for Reporting Services. Wraps the + `ListSSLCertificates` CIM method. - Added public command `Get-SqlDscRSIPAddress` to list IP addresses available for URL reservations. Wraps the `ListIPAddresses` CIM method. - Added public command `Get-SqlDscRSDatabaseInstallation` to determine whether diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6d97faac4..82d1d8f14 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -712,6 +712,83 @@ stages: testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' testRunTitle: 'Integration Commands ($(TEST_CONFIGURATION) / $(JOB_VMIMAGE))' + - stage: Integration_Test_Commands_BIReportServer_Secure + displayName: 'Integration Test Commands - BI Report Server SSL/TLS' + dependsOn: Build #Integration_Test_Commands_SqlServer + jobs: + - job: Test_Integration + displayName: 'Commands' + strategy: + matrix: + PowerBI_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_PowerBI' + PowerBI_WIN2025: + JOB_VMIMAGE: 'windows-2025' + TEST_CONFIGURATION: 'Integration_PowerBI' + pool: + vmImage: $(JOB_VMIMAGE) + timeoutInMinutes: '0' + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: PowerShell@2 + name: configureWinRM + displayName: 'Configure WinRM' + inputs: + targetType: 'inline' + script: 'winrm quickconfig -quiet' + pwsh: false + - powershell: | + Import-Module -Name ./tests/TestHelpers/CommonTestHelper.psm1 + Remove-PowerShellModuleFromCI -Name @('SqlServer', 'SQLPS') + Remove-Module -Name CommonTestHelper + name: cleanCIWorker + displayName: 'Clean CI worker' + - powershell: | + ./build.ps1 -Tasks test -CodeCoverageThreshold 0 -PesterTag $(TEST_CONFIGURATION) -PesterPath @( + # Run the integration tests in a specific group order. + # Group 0 + 'tests/Integration/Commands/Prerequisites.Integration.Tests.ps1' + 'tests/Integration/Commands/Prerequisites.RSDB.Integration.Tests.ps1' + 'tests/Integration/Commands/Save-SqlDscSqlServerMediaFile.Integration.Tests.ps1' + 'tests/Integration/Commands/Import-SqlDscPreferredModule.Integration.Tests.ps1' + # Group 1 + 'tests/Integration/Commands/Install-SqlDscPowerBIReportServer.Integration.Tests.ps1' + # Group 2 + 'tests/Integration/Commands/Request-SqlDscRSDatabaseScript.Integration.Tests.ps1' + 'tests/Integration/Commands/Request-SqlDscRSDatabaseRightsScript.Integration.Tests.ps1' + # Group 3 + 'tests/Integration/Commands/Enable-SqlDscRsSecureConnection.Integration.Tests.ps1' + 'tests/Integration/Commands/Set-SqlDscRSVirtualDirectory.Integration.Tests.ps1' + 'tests/Integration/Commands/Pre.Set-SqlDscRSUrlReservation.Integration.Tests.ps1' + 'tests/Integration/Commands/Add-SqlDscRSSslCertificateBinding.Integration.Tests.ps1' + 'tests/Integration/Commands/Set-SqlDscRSDatabaseConnection.Integration.Tests.ps1' + 'tests/Integration/Commands/Restart-SqlDscRSService.Integration.Tests.ps1' + # Group 4 + 'tests/Integration/Commands/Initialize-SqlDscRS.Integration.Tests.ps1' + # Group 5 - Post-initialization validation + 'tests/Integration/Commands/Post.Certificate.RS.Integration.Tests.ps1' + # Group 6 + 'tests/Integration/Commands/Get-SqlDscRSSslCertificate.Integration.Tests.ps1' + 'tests/Integration/Commands/Get-SqlDscRSSslCertificateBinding.Integration.Tests.ps1' + 'tests/Integration/Commands/Remove-SqlDscRSSslCertificateBinding.Integration.Tests.ps1' + 'tests/Integration/Commands/Set-SqlDscRSSslCertificateBinding.Integration.Tests.ps1' + ) + name: test + displayName: 'Run Integration Test' + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Integration Commands ($(TEST_CONFIGURATION) / $(JOB_VMIMAGE))' + - stage: Integration_Test_Resources_SqlServer displayName: 'Integration Test Resources - SQL Server' dependsOn: Quality_Test_and_Unit_Test diff --git a/source/Public/Add-SqlDscRSSslCertificateBinding.ps1 b/source/Public/Add-SqlDscRSSslCertificateBinding.ps1 new file mode 100644 index 000000000..df8119413 --- /dev/null +++ b/source/Public/Add-SqlDscRSSslCertificateBinding.ps1 @@ -0,0 +1,190 @@ +<# + .SYNOPSIS + Adds an SSL certificate binding for SQL Server Reporting Services. + + .DESCRIPTION + Adds an SSL certificate binding for SQL Server Reporting Services or + Power BI Report Server by calling the `CreateSSLCertificateBinding` + method on the `MSReportServer_ConfigurationSetting` CIM instance. + + This command binds an SSL certificate to a specific application, + IP address, and port for the Reporting Services instance. URL reservations + must be set prior for the specified application to determine if the TLS/SSL + certificate binding is valid. + + The configuration CIM instance can be obtained using the + `Get-SqlDscRSConfiguration` command and passed via the pipeline. + + .PARAMETER Configuration + Specifies the `MSReportServer_ConfigurationSetting` CIM instance for + the Reporting Services instance. This can be obtained using the + `Get-SqlDscRSConfiguration` command. This parameter accepts pipeline + input. + + .PARAMETER Application + Specifies the application for which to create the SSL binding. + Valid values are: + - 'ReportServerWebService': The Report Server Web Service. + - 'ReportServerWebApp': The Reports web application (SQL Server 2016+). + - 'ReportManager': The Report Manager (SQL Server 2014 and earlier). + + .PARAMETER CertificateHash + Specifies the thumbprint (hash) of the SSL certificate to bind. + The certificate must be installed in the local machine certificate + store. + + .PARAMETER IPAddress + Specifies the IP address for the SSL binding. Use '0.0.0.0' to bind + to all IP addresses. Default value is '0.0.0.0'. + + .PARAMETER Port + Specifies the port number for the SSL binding. Default value is 443. + + .PARAMETER Lcid + Specifies the language code identifier (LCID) for the operation. + If not specified, defaults to the operating system language. Common + values include 1033 for English (US). + + .PARAMETER PassThru + If specified, returns the configuration CIM instance after adding + the SSL certificate binding. + + .PARAMETER Force + If specified, suppresses the confirmation prompt. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Add-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'A1B2C3D4E5F6...' + + Adds an SSL certificate binding for the Report Server Web Service + using the default IP address (0.0.0.0) and port (443). + + .EXAMPLE + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + Add-SqlDscRSSslCertificateBinding -Configuration $config -Application 'ReportServerWebApp' -CertificateHash 'A1B2C3D4E5F6...' -Port 8443 -Confirm:$false + + Adds an SSL certificate binding on port 8443 without confirmation. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Add-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'A1B2C3D4E5F6...' -PassThru + + Adds the SSL binding and returns the configuration CIM instance. + + .INPUTS + `Microsoft.Management.Infrastructure.CimInstance` + + Accepts MSReportServer_ConfigurationSetting CIM instance via pipeline. + + .OUTPUTS + None. By default, this command does not generate any output. + + .OUTPUTS + `Microsoft.Management.Infrastructure.CimInstance` + + When PassThru is specified, returns the MSReportServer_ConfigurationSetting + CIM instance. + + .NOTES + The Reporting Services service may need to be restarted for the change + to take effect. + + .LINK + https://docs.microsoft.com/en-us/sql/reporting-services/wmi-provider-library-reference/configurationsetting-method-createsslcertificatebinding +#> +function Add-SqlDscRSSslCertificateBinding +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] + [OutputType([System.Object])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Object] + $Configuration, + + [Parameter(Mandatory = $true)] + [ValidateSet('ReportServerWebService', 'ReportServerWebApp', 'ReportManager')] + [System.String] + $Application, + + [Parameter(Mandatory = $true)] + [System.String] + $CertificateHash, + + [Parameter()] + [System.String] + $IPAddress = '0.0.0.0', + + [Parameter()] + [System.Int32] + $Port = 443, + + [Parameter()] + [System.Int32] + $Lcid, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + $instanceName = $Configuration.InstanceName + + if (-not $PSBoundParameters.ContainsKey('Lcid')) + { + $Lcid = (Get-OperatingSystem).OSLanguage + } + + Write-Verbose -Message ($script:localizedData.Add_SqlDscRSSslCertificateBinding_Adding -f $CertificateHash, $Application, $instanceName) + + $descriptionMessage = $script:localizedData.Add_SqlDscRSSslCertificateBinding_ShouldProcessDescription -f $CertificateHash, $Application, $instanceName + $confirmationMessage = $script:localizedData.Add_SqlDscRSSslCertificateBinding_ShouldProcessConfirmation -f $CertificateHash, $Application + $captionMessage = $script:localizedData.Add_SqlDscRSSslCertificateBinding_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) + { + $invokeRsCimMethodParameters = @{ + CimInstance = $Configuration + MethodName = 'CreateSSLCertificateBinding' + Arguments = @{ + Application = $Application + CertificateHash = $CertificateHash.ToLower() + IPAddress = $IPAddress + Port = $Port + Lcid = $Lcid + } + } + + try + { + $null = Invoke-RsCimMethod @invokeRsCimMethodParameters -ErrorAction 'Stop' + } + catch + { + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + ($script:localizedData.Add_SqlDscRSSslCertificateBinding_FailedToAdd -f $instanceName, $_.Exception.Message), + 'ASRSSCB0001', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $Configuration + ) + ) + } + } + + if ($PassThru.IsPresent) + { + return $Configuration + } + } +} diff --git a/source/Public/Get-SqlDscRSDatabaseInstallation.ps1 b/source/Public/Get-SqlDscRSDatabaseInstallation.ps1 index f952cd79e..3f5f0ebbb 100644 --- a/source/Public/Get-SqlDscRSDatabaseInstallation.ps1 +++ b/source/Public/Get-SqlDscRSDatabaseInstallation.ps1 @@ -99,14 +99,13 @@ function Get-SqlDscRSDatabaseInstallation } catch { - $PSCmdlet.ThrowTerminatingError( - [System.Management.Automation.ErrorRecord]::new( - ($script:localizedData.Get_SqlDscRSDatabaseInstallation_FailedToGet -f $instanceName, $_.Exception.Message), - 'GSRSDI0001', - [System.Management.Automation.ErrorCategory]::InvalidOperation, - $Configuration - ) - ) + $errorMessage = $script:localizedData.Get_SqlDscRSDatabaseInstallation_FailedToGet -f $instanceName + + $exception = New-Exception -Message $errorMessage -ErrorRecord $_ + + $errorRecord = New-ErrorRecord -Exception $exception -ErrorId 'GSRSDI0001' -ErrorCategory ([System.Management.Automation.ErrorCategory]::InvalidOperation) -TargetObject $Configuration + + $PSCmdlet.ThrowTerminatingError($errorRecord) } } } diff --git a/source/Public/Get-SqlDscRSSslCertificate.ps1 b/source/Public/Get-SqlDscRSSslCertificate.ps1 new file mode 100644 index 000000000..9eb43f51f --- /dev/null +++ b/source/Public/Get-SqlDscRSSslCertificate.ps1 @@ -0,0 +1,110 @@ +<# + .SYNOPSIS + Gets the available SSL certificates for SQL Server Reporting Services. + + .DESCRIPTION + Gets the SSL certificates available on the machine for use with + SQL Server Reporting Services or Power BI Report Server by calling + the `ListSSLCertificates` method on the + `MSReportServer_ConfigurationSetting` CIM instance. + + This command returns information about certificates that can be + bound to the Reporting Services instance for SSL/TLS connections. + + The configuration CIM instance can be obtained using the + `Get-SqlDscRSConfiguration` command and passed via the pipeline. + + .PARAMETER Configuration + Specifies the `MSReportServer_ConfigurationSetting` CIM instance for + the Reporting Services instance. This can be obtained using the + `Get-SqlDscRSConfiguration` command. This parameter accepts pipeline + input. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Get-SqlDscRSSslCertificate + + Gets all available SSL certificates for the Reporting Services instance. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'PBIRS' | Get-SqlDscRSSslCertificate + + Gets all available SSL certificates for Power BI Report Server. + + .INPUTS + `Microsoft.Management.Infrastructure.CimInstance` + + Accepts MSReportServer_ConfigurationSetting CIM instance via pipeline. + + .OUTPUTS + `System.Management.Automation.PSCustomObject` + + Returns objects with properties: CertificateName, HostName, + and CertificateHash. + + .NOTES + This command calls the WMI method `ListSSLCertificates`. This method + does not require an LCID parameter as it simply lists available + certificates on the machine. + + .LINK + https://docs.microsoft.com/en-us/sql/reporting-services/wmi-provider-library-reference/configurationsetting-method-listsslcertificates +#> +function Get-SqlDscRSSslCertificate +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] + [CmdletBinding()] + [OutputType([System.Management.Automation.PSCustomObject])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Object] + $Configuration + ) + + process + { + $instanceName = $Configuration.InstanceName + + Write-Verbose -Message ($script:localizedData.Get_SqlDscRSSslCertificate_Getting -f $instanceName) + + $invokeRsCimMethodParameters = @{ + CimInstance = $Configuration + MethodName = 'ListSSLCertificates' + } + + try + { + $result = Invoke-RsCimMethod @invokeRsCimMethodParameters -ErrorAction 'Stop' + + <# + The WMI method returns multiple parallel arrays: + - CertName: Array of certificate friendly names + - HostName: Array of host names for the certificates + - CertificateHash: Array of certificate thumbprints + - Length: The length of the arrays + #> + if ($result.Length -gt 0) + { + for ($i = 0; $i -lt $result.Length; $i++) + { + [PSCustomObject] @{ + CertificateName = $result.CertName[$i] + HostName = $result.HostName[$i] + CertificateHash = $result.CertificateHash[$i] + } + } + } + } + catch + { + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + ($script:localizedData.Get_SqlDscRSSslCertificate_FailedToGet -f $instanceName, $_.Exception.Message), + 'GSRSSC0001', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $Configuration + ) + ) + } + } +} diff --git a/source/Public/Get-SqlDscRSSslCertificateBinding.ps1 b/source/Public/Get-SqlDscRSSslCertificateBinding.ps1 new file mode 100644 index 000000000..9a91752d5 --- /dev/null +++ b/source/Public/Get-SqlDscRSSslCertificateBinding.ps1 @@ -0,0 +1,128 @@ +<# + .SYNOPSIS + Gets SSL certificate bindings for SQL Server Reporting Services. + + .DESCRIPTION + Gets the SSL certificate bindings for SQL Server Reporting Services or + Power BI Report Server by calling the `ListSSLCertificateBindings` method + on the `MSReportServer_ConfigurationSetting` CIM instance. + + This command retrieves information about which SSL certificates are + bound to the Reporting Services instance and on which IP addresses + and ports. + + The configuration CIM instance can be obtained using the + `Get-SqlDscRSConfiguration` command and passed via the pipeline. + + .PARAMETER Configuration + Specifies the `MSReportServer_ConfigurationSetting` CIM instance for + the Reporting Services instance. This can be obtained using the + `Get-SqlDscRSConfiguration` command. This parameter accepts pipeline + input. + + .PARAMETER Lcid + Specifies the language code identifier (LCID) for the request. + If not specified, defaults to the operating system language. Common + values include 1033 for English (US). + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Get-SqlDscRSSslCertificateBinding + + Gets all SSL certificate bindings for the Reporting Services instance 'SSRS'. + + .EXAMPLE + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + Get-SqlDscRSSslCertificateBinding -Configuration $config -Lcid 1033 + + Gets all SSL certificate bindings with a specific LCID. + + .INPUTS + `Microsoft.Management.Infrastructure.CimInstance` + + Accepts MSReportServer_ConfigurationSetting CIM instance via pipeline. + + .OUTPUTS + `System.Management.Automation.PSCustomObject` + + Returns objects with properties: Application, CertificateHash, IPAddress, + Port, and Lcid. + + .NOTES + This command calls the WMI method `ListSSLCertificateBindings`. + + .LINK + https://docs.microsoft.com/en-us/sql/reporting-services/wmi-provider-library-reference/configurationsetting-method-listsslcertificatebindings +#> +function Get-SqlDscRSSslCertificateBinding +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] + [CmdletBinding()] + [OutputType([System.Management.Automation.PSCustomObject])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Object] + $Configuration, + + [Parameter()] + [System.Int32] + $Lcid + ) + + process + { + $instanceName = $Configuration.InstanceName + + if (-not $PSBoundParameters.ContainsKey('Lcid')) + { + $Lcid = (Get-OperatingSystem).OSLanguage + } + + Write-Verbose -Message ($script:localizedData.Get_SqlDscRSSslCertificateBinding_Getting -f $instanceName) + + $invokeRsCimMethodParameters = @{ + CimInstance = $Configuration + MethodName = 'ListSSLCertificateBindings' + Arguments = @{ + Lcid = $Lcid + } + } + + try + { + $result = Invoke-RsCimMethod @invokeRsCimMethodParameters -ErrorAction 'Stop' + + <# + The WMI method returns multiple parallel arrays: + - Application: Array of application names + - CertificateHash: Array of certificate thumbprints + - IPAddress: Array of IP addresses + - Port: Array of port numbers + #> + if ($result.Application) + { + for ($i = 0; $i -lt $result.Application.Count; $i++) + { + [PSCustomObject] @{ + Application = $result.Application[$i] + CertificateHash = $result.CertificateHash[$i] + IPAddress = $result.IPAddress[$i] + Port = $result.Port[$i] + Lcid = $Lcid + } + } + } + } + catch + { + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + ($script:localizedData.Get_SqlDscRSSslCertificateBinding_FailedToGet -f $instanceName, $_.Exception.Message), + 'GSRSSCB0001', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $Configuration + ) + ) + } + } +} diff --git a/source/Public/Remove-SqlDscRSSslCertificateBinding.ps1 b/source/Public/Remove-SqlDscRSSslCertificateBinding.ps1 new file mode 100644 index 000000000..6f3fe94d8 --- /dev/null +++ b/source/Public/Remove-SqlDscRSSslCertificateBinding.ps1 @@ -0,0 +1,188 @@ +<# + .SYNOPSIS + Removes an SSL certificate binding from SQL Server Reporting Services. + + .DESCRIPTION + Removes an SSL certificate binding from SQL Server Reporting Services + or Power BI Report Server by calling the `RemoveSSLCertificateBindings` + method on the `MSReportServer_ConfigurationSetting` CIM instance. + + This command removes an SSL certificate binding for a specific + application, certificate, IP address, and port from the Reporting + Services instance. + + The configuration CIM instance can be obtained using the + `Get-SqlDscRSConfiguration` command and passed via the pipeline. + + .PARAMETER Configuration + Specifies the `MSReportServer_ConfigurationSetting` CIM instance for + the Reporting Services instance. This can be obtained using the + `Get-SqlDscRSConfiguration` command. This parameter accepts pipeline + input. + + .PARAMETER Application + Specifies the application from which to remove the SSL binding. + Valid values are: + - 'ReportServerWebService': The Report Server Web Service. + - 'ReportServerWebApp': The Reports web application (SQL Server 2016+). + - 'ReportManager': The Report Manager (SQL Server 2014 and earlier). + + .PARAMETER CertificateHash + Specifies the thumbprint (hash) of the SSL certificate to unbind. + + .PARAMETER IPAddress + Specifies the IP address of the SSL binding to remove. Use '0.0.0.0' + for all IP addresses. Default value is '0.0.0.0'. + + .PARAMETER Port + Specifies the port number of the SSL binding to remove. Default value + is 443. + + .PARAMETER Lcid + Specifies the language code identifier (LCID) for the operation. + If not specified, defaults to the operating system language. Common + values include 1033 for English (US). + + .PARAMETER PassThru + If specified, returns the configuration CIM instance after removing + the SSL certificate binding. + + .PARAMETER Force + If specified, suppresses the confirmation prompt. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Remove-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'A1B2C3D4E5F6...' + + Removes the SSL certificate binding for the Report Server Web Service. + + .EXAMPLE + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + Remove-SqlDscRSSslCertificateBinding -Configuration $config -Application 'ReportServerWebApp' -CertificateHash 'A1B2C3D4E5F6...' -Port 8443 -Confirm:$false + + Removes an SSL certificate binding on port 8443 without confirmation. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Remove-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'A1B2C3D4E5F6...' -Force -PassThru + + Removes the SSL binding without confirmation and returns the + configuration CIM instance. + + .INPUTS + `Microsoft.Management.Infrastructure.CimInstance` + + Accepts MSReportServer_ConfigurationSetting CIM instance via pipeline. + + .OUTPUTS + None. By default, this command does not generate any output. + + .OUTPUTS + `Microsoft.Management.Infrastructure.CimInstance` + + When PassThru is specified, returns the MSReportServer_ConfigurationSetting + CIM instance. + + .NOTES + The Reporting Services service may need to be restarted for the change + to take effect. + + .LINK + https://docs.microsoft.com/en-us/sql/reporting-services/wmi-provider-library-reference/configurationsetting-method-removesslcertificatebindings +#> +function Remove-SqlDscRSSslCertificateBinding +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] + [OutputType([System.Object])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Object] + $Configuration, + + [Parameter(Mandatory = $true)] + [ValidateSet('ReportServerWebService', 'ReportServerWebApp', 'ReportManager')] + [System.String] + $Application, + + [Parameter(Mandatory = $true)] + [System.String] + $CertificateHash, + + [Parameter()] + [System.String] + $IPAddress = '0.0.0.0', + + [Parameter()] + [System.Int32] + $Port = 443, + + [Parameter()] + [System.Int32] + $Lcid, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + $instanceName = $Configuration.InstanceName + + if (-not $PSBoundParameters.ContainsKey('Lcid')) + { + $Lcid = (Get-OperatingSystem).OSLanguage + } + + Write-Verbose -Message ($script:localizedData.Remove_SqlDscRSSslCertificateBinding_Removing -f $CertificateHash, $Application, $instanceName) + + $descriptionMessage = $script:localizedData.Remove_SqlDscRSSslCertificateBinding_ShouldProcessDescription -f $CertificateHash, $Application, $instanceName + $confirmationMessage = $script:localizedData.Remove_SqlDscRSSslCertificateBinding_ShouldProcessConfirmation -f $CertificateHash, $Application + $captionMessage = $script:localizedData.Remove_SqlDscRSSslCertificateBinding_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) + { + $invokeRsCimMethodParameters = @{ + CimInstance = $Configuration + MethodName = 'RemoveSSLCertificateBindings' + Arguments = @{ + Application = $Application + CertificateHash = $CertificateHash.ToLower() + IPAddress = $IPAddress + Port = $Port + Lcid = $Lcid + } + } + + try + { + $null = Invoke-RsCimMethod @invokeRsCimMethodParameters -ErrorAction 'Stop' + } + catch + { + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + ($script:localizedData.Remove_SqlDscRSSslCertificateBinding_FailedToRemove -f $instanceName, $_.Exception.Message), + 'RSRSSCB0001', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $Configuration + ) + ) + } + } + + if ($PassThru.IsPresent) + { + return $Configuration + } + } +} diff --git a/source/Public/Set-SqlDscRSSslCertificateBinding.ps1 b/source/Public/Set-SqlDscRSSslCertificateBinding.ps1 new file mode 100644 index 000000000..3633ebc97 --- /dev/null +++ b/source/Public/Set-SqlDscRSSslCertificateBinding.ps1 @@ -0,0 +1,204 @@ +<# + .SYNOPSIS + Sets SSL certificate bindings for SQL Server Reporting Services. + + .DESCRIPTION + Sets the SSL certificate bindings for SQL Server Reporting Services + or Power BI Report Server by managing bindings through the + `MSReportServer_ConfigurationSetting` CIM instance. + + This command replaces existing SSL certificate bindings for a specific + application with the specified binding. Any existing bindings for the + application that don't match will be removed. + + The configuration CIM instance can be obtained using the + `Get-SqlDscRSConfiguration` command and passed via the pipeline. + + .PARAMETER Configuration + Specifies the `MSReportServer_ConfigurationSetting` CIM instance for + the Reporting Services instance. This can be obtained using the + `Get-SqlDscRSConfiguration` command. This parameter accepts pipeline + input. + + .PARAMETER Application + Specifies the application for which to set the SSL binding. + Valid values are: + - 'ReportServerWebService': The Report Server Web Service. + - 'ReportServerWebApp': The Reports web application (SQL Server 2016+). + - 'ReportManager': The Report Manager (SQL Server 2014 and earlier). + + .PARAMETER CertificateHash + Specifies the thumbprint (hash) of the SSL certificate to bind. + The certificate must be installed in the local machine certificate + store. + + .PARAMETER IPAddress + Specifies the IP address for the SSL binding. Use '0.0.0.0' to bind + to all IP addresses. Default value is '0.0.0.0'. + + .PARAMETER Port + Specifies the port number for the SSL binding. Default value is 443. + + .PARAMETER Lcid + Specifies the language code identifier (LCID) for the operation. + If not specified, defaults to the operating system language. Common + values include 1033 for English (US). + + .PARAMETER PassThru + If specified, returns the configuration CIM instance after setting + the SSL certificate binding. + + .PARAMETER Force + If specified, suppresses the confirmation prompt. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'A1B2C3D4E5F6...' + + Sets the SSL certificate binding for the Report Server Web Service, + removing any existing bindings for that application. + + .EXAMPLE + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + Set-SqlDscRSSslCertificateBinding -Configuration $config -Application 'ReportServerWebApp' -CertificateHash 'A1B2C3D4E5F6...' -Port 8443 -Confirm:$false + + Sets an SSL certificate binding on port 8443 without confirmation. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'A1B2C3D4E5F6...' -PassThru + + Sets the SSL binding and returns the configuration CIM instance. + + .INPUTS + `Microsoft.Management.Infrastructure.CimInstance` + + Accepts MSReportServer_ConfigurationSetting CIM instance via pipeline. + + .OUTPUTS + None. By default, this command does not generate any output. + + .OUTPUTS + `Microsoft.Management.Infrastructure.CimInstance` + + When PassThru is specified, returns the MSReportServer_ConfigurationSetting + CIM instance. + + .NOTES + The Reporting Services service may need to be restarted for the change + to take effect. Existing SSL bindings for the application that do not + match the specified parameters will be removed. + + .LINK + https://docs.microsoft.com/en-us/sql/reporting-services/wmi-provider-library-reference/configurationsetting-method-createsslcertificatebinding +#> +function Set-SqlDscRSSslCertificateBinding +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] + [OutputType([System.Object])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Object] + $Configuration, + + [Parameter(Mandatory = $true)] + [ValidateSet('ReportServerWebService', 'ReportServerWebApp', 'ReportManager')] + [System.String] + $Application, + + [Parameter(Mandatory = $true)] + [System.String] + $CertificateHash, + + [Parameter()] + [System.String] + $IPAddress = '0.0.0.0', + + [Parameter()] + [System.Int32] + $Port = 443, + + [Parameter()] + [System.Int32] + $Lcid, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + process + { + if ($Force.IsPresent -and -not $Confirm) + { + $ConfirmPreference = 'None' + } + + $instanceName = $Configuration.InstanceName + + if (-not $PSBoundParameters.ContainsKey('Lcid')) + { + $Lcid = (Get-OperatingSystem).OSLanguage + } + + $descriptionMessage = $script:localizedData.Set_SqlDscRSSslCertificateBinding_ShouldProcessDescription -f $CertificateHash, $Application, $instanceName + $confirmationMessage = $script:localizedData.Set_SqlDscRSSslCertificateBinding_ShouldProcessConfirmation -f $CertificateHash, $Application + $captionMessage = $script:localizedData.Set_SqlDscRSSslCertificateBinding_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) + { + # Get current bindings + $currentBindings = $Configuration | Get-SqlDscRSSslCertificateBinding -Lcid $Lcid + + # Filter bindings for the specified application + $applicationBindings = $currentBindings | Where-Object -FilterScript { + $_.Application -eq $Application + } + + # Normalize the certificate hash for comparison + $normalizedHash = $CertificateHash.ToLower() + + # Remove bindings that don't match the desired configuration + foreach ($binding in $applicationBindings) + { + $shouldRemove = $binding.CertificateHash -ne $normalizedHash -or + $binding.IPAddress -ne $IPAddress -or + $binding.Port -ne $Port + + if ($shouldRemove) + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscRSSslCertificateBinding_RemovingExisting -f $binding.CertificateHash, $Application, $instanceName) + + $Configuration | Remove-SqlDscRSSslCertificateBinding -Application $Application -CertificateHash $binding.CertificateHash -IPAddress $binding.IPAddress -Port $binding.Port -Lcid $Lcid -Force -ErrorAction 'Stop' + } + } + + # Check if the desired binding already exists + $bindingExists = $applicationBindings | Where-Object -FilterScript { + $_.CertificateHash -eq $normalizedHash -and + $_.IPAddress -eq $IPAddress -and + $_.Port -eq $Port + } + + if (-not $bindingExists) + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscRSSslCertificateBinding_Adding -f $CertificateHash, $Application, $instanceName) + + $Configuration | Add-SqlDscRSSslCertificateBinding -Application $Application -CertificateHash $CertificateHash -IPAddress $IPAddress -Port $Port -Lcid $Lcid -Force -ErrorAction 'Stop' + } + else + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscRSSslCertificateBinding_AlreadyExists -f $CertificateHash, $Application, $instanceName) + } + } + + if ($PassThru.IsPresent) + { + return $Configuration + } + } +} diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index c0942bf40..822b23d4c 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -830,6 +830,35 @@ ConvertFrom-StringData @' Restart_SqlDscRSService_WaitingBeforeStart = Waiting {0} seconds before starting service '{1}'. Restart_SqlDscRSService_StartingDependentService = Starting dependent service '{0}'. + ## Get-SqlDscRSSslCertificateBinding + Get_SqlDscRSSslCertificateBinding_Getting = Getting SSL certificate bindings for Reporting Services instance '{0}'. + Get_SqlDscRSSslCertificateBinding_FailedToGet = Failed to get SSL certificate bindings for Reporting Services instance '{0}'. {1} (GSRSSCB0001) + + ## Add-SqlDscRSSslCertificateBinding + Add_SqlDscRSSslCertificateBinding_Adding = Adding SSL certificate binding '{0}' for application '{1}' on Reporting Services instance '{2}'. + Add_SqlDscRSSslCertificateBinding_ShouldProcessDescription = Adding SSL certificate binding '{0}' for application '{1}' on Reporting Services instance '{2}'. + Add_SqlDscRSSslCertificateBinding_ShouldProcessConfirmation = Are you sure you want to add SSL certificate binding '{0}' for application '{1}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Add_SqlDscRSSslCertificateBinding_ShouldProcessCaption = Add SSL certificate binding for Reporting Services instance + Add_SqlDscRSSslCertificateBinding_FailedToAdd = Failed to add SSL certificate binding for Reporting Services instance '{0}'. {1} (ASRSSCB0001) + + ## Remove-SqlDscRSSslCertificateBinding + Remove_SqlDscRSSslCertificateBinding_Removing = Removing SSL certificate binding '{0}' for application '{1}' from Reporting Services instance '{2}'. + Remove_SqlDscRSSslCertificateBinding_ShouldProcessDescription = Removing SSL certificate binding '{0}' for application '{1}' from Reporting Services instance '{2}'. + Remove_SqlDscRSSslCertificateBinding_ShouldProcessConfirmation = Are you sure you want to remove SSL certificate binding '{0}' for application '{1}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Remove_SqlDscRSSslCertificateBinding_ShouldProcessCaption = Remove SSL certificate binding from Reporting Services instance + Remove_SqlDscRSSslCertificateBinding_FailedToRemove = Failed to remove SSL certificate binding for Reporting Services instance '{0}'. {1} (RSRSSCB0001) + + ## Set-SqlDscRSSslCertificateBinding + Set_SqlDscRSSslCertificateBinding_ShouldProcessDescription = Setting SSL certificate binding '{0}' for application '{1}' on Reporting Services instance '{2}'. + Set_SqlDscRSSslCertificateBinding_ShouldProcessConfirmation = Are you sure you want to set SSL certificate binding '{0}' for application '{1}'? Existing bindings will be removed. + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Set_SqlDscRSSslCertificateBinding_ShouldProcessCaption = Set SSL certificate binding for Reporting Services instance + Set_SqlDscRSSslCertificateBinding_RemovingExisting = Removing existing SSL certificate binding '{0}' for application '{1}' from Reporting Services instance '{2}'. + Set_SqlDscRSSslCertificateBinding_Adding = Adding SSL certificate binding '{0}' for application '{1}' on Reporting Services instance '{2}'. + Set_SqlDscRSSslCertificateBinding_AlreadyExists = SSL certificate binding '{0}' for application '{1}' already exists on Reporting Services instance '{2}'. + ## New-SqlDscRSEncryptionKey New_SqlDscRSEncryptionKey_Generating = Generating new encryption key for Reporting Services instance '{0}'. New_SqlDscRSEncryptionKey_ShouldProcessDescription = Generating new encryption key for Reporting Services instance '{0}'. This will invalidate existing encryption key backups. @@ -881,13 +910,17 @@ ConvertFrom-StringData @' Initialize_SqlDscRS_ShouldProcessCaption = Initialize Reporting Services instance Initialize_SqlDscRS_FailedToInitialize = Failed to initialize Reporting Services instance '{0}'. {1} (ISRS0001) + ## Get-SqlDscRSSslCertificate + Get_SqlDscRSSslCertificate_Getting = Getting available SSL certificates for Reporting Services instance '{0}'. + Get_SqlDscRSSslCertificate_FailedToGet = Failed to get available SSL certificates for Reporting Services instance '{0}'. {1} (GSRSSC0001) + ## Get-SqlDscRSIPAddress Get_SqlDscRSIPAddress_Getting = Getting available IP addresses for Reporting Services instance '{0}'. Get_SqlDscRSIPAddress_FailedToGet = Failed to get available IP addresses for Reporting Services instance '{0}'. {1} (GSRSIP0001) ## Get-SqlDscRSDatabaseInstallation Get_SqlDscRSDatabaseInstallation_Getting = Getting report server installations registered in the database for Reporting Services instance '{0}'. - Get_SqlDscRSDatabaseInstallation_FailedToGet = Failed to get report server installations for Reporting Services instance '{0}'. {1} (GSRSDI0001) + Get_SqlDscRSDatabaseInstallation_FailedToGet = Failed to get report server installations for Reporting Services instance '{0}'. (GSRSDI0001) ## Request-SqlDscRSDatabaseUpgradeScript Request_SqlDscRSDatabaseUpgradeScript_Generating = Generating database upgrade script for Reporting Services instance '{0}'. diff --git a/tests/Integration/Commands/Add-SqlDscRSSslCertificateBinding.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscRSSslCertificateBinding.Integration.Tests.ps1 new file mode 100644 index 000000000..813a5afff --- /dev/null +++ b/tests/Integration/Commands/Add-SqlDscRSSslCertificateBinding.Integration.Tests.ps1 @@ -0,0 +1,144 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + # Do not use -Force. Doing so, or unloading the module in AfterAll, causes + # PowerShell class types to get new identities, breaking type comparisons. + Import-Module -Name $script:moduleName -ErrorAction 'Stop' + + Import-Module -Name 'PSPKI' -ErrorAction 'Stop' + + $script:computerName = Get-ComputerName + + <# + Create a self-signed certificate for SSL binding tests. + Using PSPKI module's New-SelfSignedCertificateEx for compatibility. + #> + $newSelfSignedCertificateExParameters = @{ + Subject = "CN=$script:computerName" + EKU = 'Server Authentication' + KeyUsage = 'DigitalSignature, KeyEncipherment, DataEncipherment' + SAN = "dns:$script:computerName" + FriendlyName = 'SqlServerDsc SSL Integration Test Certificate' + Exportable = $true + KeyLength = 2048 + ProviderName = 'Microsoft Software Key Storage Provider' + AlgorithmName = 'RSA' + SignatureAlgorithm = 'SHA256' + StoreLocation = 'LocalMachine' + } + + $script:testCertificate = New-SelfSignedCertificateEx @newSelfSignedCertificateExParameters + + Write-Verbose -Message ('Created self-signed certificate ''{0}'' with thumbprint ''{1}''.' -f $script:testCertificate.Subject, $script:testCertificate.Thumbprint) -Verbose + + # Add the certificate to Trusted Root Certification Authorities to avoid browser warnings + $script:certificatePath = Join-Path -Path $env:TEMP -ChildPath 'SqlServerDscSslIntegrationTest.cer' + + Export-Certificate -Cert $script:testCertificate -FilePath $script:certificatePath -ErrorAction 'Stop' + + $null = Import-Certificate -FilePath $script:certificatePath -CertStoreLocation 'Cert:\LocalMachine\Root' -ErrorAction 'Stop' + + Write-Verbose -Message ('Added certificate to Trusted Root Certification Authorities.') -Verbose + + $script:testCertificateHash = $script:testCertificate.Thumbprint + $script:testIPAddress = '0.0.0.0' + $script:testPort = 443 +} + +<# + .NOTES + These tests use a self-signed certificate created in BeforeAll. + The certificate is not removed after tests to allow inspection. +#> +Describe 'Add-SqlDscRSSslCertificateBinding' { + BeforeAll { + $script:addSslCertificateBindingParameters = @{ + CertificateHash = $script:testCertificateHash + IPAddress = $script:testIPAddress + Port = $script:testPort + Force = $true + ErrorAction = 'Stop' + } + } + + Context 'When adding SSL certificate binding for SQL Server Reporting Services' -Tag @('Integration_SQL2017_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should add SSL certificate binding for ReportServerWebService' { + { $script:configuration | Add-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' @script:addSslCertificateBindingParameters } | Should -Not -Throw + } + + It 'Should add SSL certificate binding for ReportServerWebApp' { + { $script:configuration | Add-SqlDscRSSslCertificateBinding -Application 'ReportServerWebApp' @script:addSslCertificateBindingParameters } | Should -Not -Throw + } + } + + Context 'When adding SSL certificate binding for SQL Server Reporting Services' -Tag @('Integration_SQL2019_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should add SSL certificate binding for ReportServerWebService' { + { $script:configuration | Add-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' @script:addSslCertificateBindingParameters } | Should -Not -Throw + } + + It 'Should add SSL certificate binding for ReportServerWebApp' { + { $script:configuration | Add-SqlDscRSSslCertificateBinding -Application 'ReportServerWebApp' @script:addSslCertificateBindingParameters } | Should -Not -Throw + } + } + + Context 'When adding SSL certificate binding for SQL Server Reporting Services' -Tag @('Integration_SQL2022_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should add SSL certificate binding for ReportServerWebService' { + { $script:configuration | Add-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' @script:addSslCertificateBindingParameters } | Should -Not -Throw + } + + It 'Should add SSL certificate binding for ReportServerWebApp' { + { $script:configuration | Add-SqlDscRSSslCertificateBinding -Application 'ReportServerWebApp' @script:addSslCertificateBindingParameters } | Should -Not -Throw + } + } + + Context 'When adding SSL certificate binding for Power BI Report Server' -Tag @('Integration_PowerBI') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + } + + It 'Should add SSL certificate binding for ReportServerWebService' { + { $script:configuration | Add-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' @script:addSslCertificateBindingParameters } | Should -Not -Throw + } + + It 'Should add SSL certificate binding for ReportServerWebApp' { + { $script:configuration | Add-SqlDscRSSslCertificateBinding -Application 'ReportServerWebApp' @script:addSslCertificateBindingParameters } | Should -Not -Throw + } + } +} diff --git a/tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1 index 988a6259a..0036d428b 100644 --- a/tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1 @@ -67,25 +67,6 @@ Describe 'Add-SqlDscRSUrlReservation' { $webAppIndex | Should -BeGreaterOrEqual 0 -Because 'ReportServerWebApp should be in the applications' $reservations.UrlString[$webAppIndex] | Should -Be $script:testUrl } - - It 'Should return configuration when using PassThru' { - # First remove the test URL if it exists - try - { - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' - $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' - } - catch - { - # Ignore - } - - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' - $result = $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -PassThru -ErrorAction 'Stop' - - $result | Should -Not -BeNullOrEmpty - $result.InstanceName | Should -Be 'SSRS' - } } Context 'When adding URL reservation for SQL Server Reporting Services' -Tag @('Integration_SQL2019_RS') { @@ -120,25 +101,6 @@ Describe 'Add-SqlDscRSUrlReservation' { $webAppIndex | Should -BeGreaterOrEqual 0 -Because 'ReportServerWebApp should be in the applications' $reservations.UrlString[$webAppIndex] | Should -Be $script:testUrl } - - It 'Should return configuration when using PassThru' { - # First remove the test URL if it exists - try - { - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' - $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' - } - catch - { - # Ignore - } - - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' - $result = $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -PassThru -ErrorAction 'Stop' - - $result | Should -Not -BeNullOrEmpty - $result.InstanceName | Should -Be 'SSRS' - } } Context 'When adding URL reservation for SQL Server Reporting Services' -Tag @('Integration_SQL2022_RS') { @@ -173,25 +135,6 @@ Describe 'Add-SqlDscRSUrlReservation' { $webAppIndex | Should -BeGreaterOrEqual 0 -Because 'ReportServerWebApp should be in the applications' $reservations.UrlString[$webAppIndex] | Should -Be $script:testUrl } - - It 'Should return configuration when using PassThru' { - # First remove the test URL if it exists - try - { - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' - $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' - } - catch - { - # Ignore - } - - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' - $result = $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -PassThru -ErrorAction 'Stop' - - $result | Should -Not -BeNullOrEmpty - $result.InstanceName | Should -Be 'SSRS' - } } Context 'When adding URL reservation for Power BI Report Server' -Tag @('Integration_PowerBI') { @@ -226,24 +169,5 @@ Describe 'Add-SqlDscRSUrlReservation' { $webAppIndex | Should -BeGreaterOrEqual 0 -Because 'ReportServerWebApp should be in the applications' $reservations.UrlString[$webAppIndex] | Should -Be $script:testUrl } - - It 'Should return configuration when using PassThru' { - # First remove the test URL if it exists - try - { - $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' - $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' - } - catch - { - # Ignore - } - - $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' - $result = $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -PassThru -ErrorAction 'Stop' - - $result | Should -Not -BeNullOrEmpty - $result.InstanceName | Should -Be 'PBIRS' - } } } diff --git a/tests/Integration/Commands/Get-SqlDscRSSslCertificate.Integration.Tests.ps1 b/tests/Integration/Commands/Get-SqlDscRSSslCertificate.Integration.Tests.ps1 new file mode 100644 index 000000000..694c8e66b --- /dev/null +++ b/tests/Integration/Commands/Get-SqlDscRSSslCertificate.Integration.Tests.ps1 @@ -0,0 +1,209 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + # Do not use -Force. Doing so, or unloading the module in AfterAll, causes + # PowerShell class types to get new identities, breaking type comparisons. + Import-Module -Name $script:moduleName -ErrorAction 'Stop' + + $script:computerName = Get-ComputerName + + <# + Get the self-signed certificate created by Add-SqlDscRSSslCertificateBinding + integration tests. This test assumes the Add-SqlDscRSSslCertificateBinding + tests have run first and created the certificate. + #> + $script:testCertificate = Get-ChildItem -Path 'Cert:\LocalMachine\My' | + Where-Object -FilterScript { + $_.FriendlyName -eq 'SqlServerDsc SSL Integration Test Certificate' -and + $_.Subject -eq "CN=$script:computerName" + } | + Select-Object -First 1 + + if (-not $script:testCertificate) + { + throw 'Test certificate not found. Ensure Add-SqlDscRSSslCertificateBinding integration tests have run first to create the certificate.' + } + + $script:testCertificateHash = $script:testCertificate.Thumbprint + + Write-Verbose -Message ('Using self-signed certificate ''{0}'' with thumbprint ''{1}''.' -f $script:testCertificate.Subject, $script:testCertificateHash) -Verbose +} + +Describe 'Get-SqlDscRSSslCertificate' { + Context 'When getting SSL certificates for SQL Server Reporting Services' -Tag @('Integration_SQL2017_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should return available SSL certificates' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return certificates with correct properties' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $result[0].PSObject.Properties.Name | Should -Contain 'CertificateName' + $result[0].PSObject.Properties.Name | Should -Contain 'HostName' + $result[0].PSObject.Properties.Name | Should -Contain 'CertificateHash' + } + + It 'Should return the test certificate that was bound' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $result.CertificateHash | Should -Contain $script:testCertificateHash + } + + It 'Should return correct properties for the test certificate' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $testCert = $result | Where-Object -FilterScript { + $_.CertificateHash -eq $script:testCertificateHash + } + + $testCert | Should -Not -BeNullOrEmpty + $testCert.CertificateName | Should -Be $script:testCertificate.FriendlyName + $testCert.HostName | Should -Be $script:computerName + } + } + + Context 'When getting SSL certificates for SQL Server Reporting Services' -Tag @('Integration_SQL2019_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should return available SSL certificates' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return certificates with correct properties' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $result[0].PSObject.Properties.Name | Should -Contain 'CertificateName' + $result[0].PSObject.Properties.Name | Should -Contain 'HostName' + $result[0].PSObject.Properties.Name | Should -Contain 'CertificateHash' + } + + It 'Should return the test certificate that was bound' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $result.CertificateHash | Should -Contain $script:testCertificateHash + } + + It 'Should return correct properties for the test certificate' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $testCert = $result | Where-Object -FilterScript { + $_.CertificateHash -eq $script:testCertificateHash + } + + $testCert | Should -Not -BeNullOrEmpty + $testCert.CertificateName | Should -Be $script:testCertificate.FriendlyName + $testCert.HostName | Should -Be $script:computerName + } + } + + Context 'When getting SSL certificates for SQL Server Reporting Services' -Tag @('Integration_SQL2022_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should return available SSL certificates' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return certificates with correct properties' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $result[0].PSObject.Properties.Name | Should -Contain 'CertificateName' + $result[0].PSObject.Properties.Name | Should -Contain 'HostName' + $result[0].PSObject.Properties.Name | Should -Contain 'CertificateHash' + } + + It 'Should return the test certificate that was bound' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $result.CertificateHash | Should -Contain $script:testCertificateHash + } + + It 'Should return correct properties for the test certificate' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $testCert = $result | Where-Object -FilterScript { + $_.CertificateHash -eq $script:testCertificateHash + } + + $testCert | Should -Not -BeNullOrEmpty + $testCert.CertificateName | Should -Be $script:testCertificate.FriendlyName + $testCert.HostName | Should -Be $script:computerName + } + } + + Context 'When getting SSL certificates for Power BI Report Server' -Tag @('Integration_PowerBI') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + } + + It 'Should return available SSL certificates' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return certificates with correct properties' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $result[0].PSObject.Properties.Name | Should -Contain 'CertificateName' + $result[0].PSObject.Properties.Name | Should -Contain 'HostName' + $result[0].PSObject.Properties.Name | Should -Contain 'CertificateHash' + } + + It 'Should return the test certificate that was bound' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $result.CertificateHash | Should -Contain $script:testCertificateHash + } + + It 'Should return correct properties for the test certificate' { + $result = $script:configuration | Get-SqlDscRSSslCertificate -ErrorAction 'Stop' + + $testCert = $result | Where-Object -FilterScript { + $_.CertificateHash -eq $script:testCertificateHash + } + + $testCert | Should -Not -BeNullOrEmpty + $testCert.CertificateName | Should -Be $script:testCertificate.FriendlyName + $testCert.HostName | Should -Be $script:computerName + } + } +} diff --git a/tests/Integration/Commands/Get-SqlDscRSSslCertificateBinding.Integration.Tests.ps1 b/tests/Integration/Commands/Get-SqlDscRSSslCertificateBinding.Integration.Tests.ps1 new file mode 100644 index 000000000..dbbc00f6f --- /dev/null +++ b/tests/Integration/Commands/Get-SqlDscRSSslCertificateBinding.Integration.Tests.ps1 @@ -0,0 +1,219 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + # Do not use -Force. Doing so, or unloading the module in AfterAll, causes + # PowerShell class types to get new identities, breaking type comparisons. + Import-Module -Name $script:moduleName -ErrorAction 'Stop' + + $script:computerName = Get-ComputerName + + <# + Get the self-signed certificate created by Add-SqlDscRSSslCertificateBinding + integration tests. This test assumes the Add-SqlDscRSSslCertificateBinding + tests have run first and created the certificate. + #> + $script:testCertificate = Get-ChildItem -Path 'Cert:\LocalMachine\My' | + Where-Object -FilterScript { + $_.FriendlyName -eq 'SqlServerDsc SSL Integration Test Certificate' -and + $_.Subject -eq "CN=$script:computerName" + } | + Select-Object -First 1 + + if (-not $script:testCertificate) + { + throw 'Test certificate not found. Ensure Add-SqlDscRSSslCertificateBinding integration tests have run first to create the certificate.' + } + + $script:testCertificateHash = $script:testCertificate.Thumbprint + $script:testIPAddress = '0.0.0.0' + $script:testPort = 443 + + Write-Verbose -Message ('Using self-signed certificate ''{0}'' with thumbprint ''{1}''.' -f $script:testCertificate.Subject, $script:testCertificateHash) -Verbose +} + +Describe 'Get-SqlDscRSSslCertificateBinding' { + Context 'When getting SSL certificate bindings for SQL Server Reporting Services' -Tag @('Integration_SQL2017_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should return SSL certificate bindings' { + $result = $script:configuration | Get-SqlDscRSSslCertificateBinding -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return the binding for the test certificate' { + $result = $script:configuration | Get-SqlDscRSSslCertificateBinding -ErrorAction 'Stop' + + $binding = $result | Where-Object -FilterScript { + $_.Application -eq 'ReportServerWebService' -and + $_.CertificateHash -eq $script:testCertificateHash -and + $_.Port -eq $script:testPort + } + + $binding | Should -Not -BeNullOrEmpty + $binding.Application | Should -Be 'ReportServerWebService' + $binding.IPAddress | Should -Be $script:testIPAddress + } + + It 'Should return the binding for ReportServerWebApp' { + $result = $script:configuration | Get-SqlDscRSSslCertificateBinding -ErrorAction 'Stop' + + $binding = $result | Where-Object -FilterScript { + $_.Application -eq 'ReportServerWebApp' -and + $_.CertificateHash -eq $script:testCertificateHash -and + $_.Port -eq $script:testPort + } + + $binding | Should -Not -BeNullOrEmpty + $binding.Application | Should -Be 'ReportServerWebApp' + $binding.IPAddress | Should -Be $script:testIPAddress + } + } + + Context 'When getting SSL certificate bindings for SQL Server Reporting Services' -Tag @('Integration_SQL2019_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should return SSL certificate bindings' { + $result = $script:configuration | Get-SqlDscRSSslCertificateBinding -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return the binding for the test certificate' { + $result = $script:configuration | Get-SqlDscRSSslCertificateBinding -ErrorAction 'Stop' + + $binding = $result | Where-Object -FilterScript { + $_.Application -eq 'ReportServerWebService' -and + $_.CertificateHash -eq $script:testCertificateHash -and + $_.Port -eq $script:testPort + } + + $binding | Should -Not -BeNullOrEmpty + $binding.Application | Should -Be 'ReportServerWebService' + $binding.IPAddress | Should -Be $script:testIPAddress + } + + It 'Should return the binding for ReportServerWebApp' { + $result = $script:configuration | Get-SqlDscRSSslCertificateBinding -ErrorAction 'Stop' + + $binding = $result | Where-Object -FilterScript { + $_.Application -eq 'ReportServerWebApp' -and + $_.CertificateHash -eq $script:testCertificateHash -and + $_.Port -eq $script:testPort + } + + $binding | Should -Not -BeNullOrEmpty + $binding.Application | Should -Be 'ReportServerWebApp' + $binding.IPAddress | Should -Be $script:testIPAddress + } + } + + Context 'When getting SSL certificate bindings for SQL Server Reporting Services' -Tag @('Integration_SQL2022_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should return SSL certificate bindings' { + $result = $script:configuration | Get-SqlDscRSSslCertificateBinding -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return the binding for the test certificate' { + $result = $script:configuration | Get-SqlDscRSSslCertificateBinding -ErrorAction 'Stop' + + $binding = $result | Where-Object -FilterScript { + $_.Application -eq 'ReportServerWebService' -and + $_.CertificateHash -eq $script:testCertificateHash -and + $_.Port -eq $script:testPort + } + + $binding | Should -Not -BeNullOrEmpty + $binding.Application | Should -Be 'ReportServerWebService' + $binding.IPAddress | Should -Be $script:testIPAddress + } + + It 'Should return the binding for ReportServerWebApp' { + $result = $script:configuration | Get-SqlDscRSSslCertificateBinding -ErrorAction 'Stop' + + $binding = $result | Where-Object -FilterScript { + $_.Application -eq 'ReportServerWebApp' -and + $_.CertificateHash -eq $script:testCertificateHash -and + $_.Port -eq $script:testPort + } + + $binding | Should -Not -BeNullOrEmpty + $binding.Application | Should -Be 'ReportServerWebApp' + $binding.IPAddress | Should -Be $script:testIPAddress + } + } + + Context 'When getting SSL certificate bindings for Power BI Report Server' -Tag @('Integration_PowerBI') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + } + + It 'Should return SSL certificate bindings' { + $result = $script:configuration | Get-SqlDscRSSslCertificateBinding -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return the binding for the test certificate' { + $result = $script:configuration | Get-SqlDscRSSslCertificateBinding -ErrorAction 'Stop' + + $binding = $result | Where-Object -FilterScript { + $_.Application -eq 'ReportServerWebService' -and + $_.CertificateHash -eq $script:testCertificateHash -and + $_.Port -eq $script:testPort + } + + $binding | Should -Not -BeNullOrEmpty + $binding.Application | Should -Be 'ReportServerWebService' + $binding.IPAddress | Should -Be $script:testIPAddress + } + + It 'Should return the binding for ReportServerWebApp' { + $result = $script:configuration | Get-SqlDscRSSslCertificateBinding -ErrorAction 'Stop' + + $binding = $result | Where-Object -FilterScript { + $_.Application -eq 'ReportServerWebApp' -and + $_.CertificateHash -eq $script:testCertificateHash -and + $_.Port -eq $script:testPort + } + + $binding | Should -Not -BeNullOrEmpty + $binding.Application | Should -Be 'ReportServerWebApp' + $binding.IPAddress | Should -Be $script:testIPAddress + } + } +} diff --git a/tests/Integration/Commands/Post.Certificate.RS.Integration.Tests.ps1 b/tests/Integration/Commands/Post.Certificate.RS.Integration.Tests.ps1 new file mode 100644 index 000000000..b2c3c282c --- /dev/null +++ b/tests/Integration/Commands/Post.Certificate.RS.Integration.Tests.ps1 @@ -0,0 +1,119 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + # Do not use -Force. Doing so, or unloading the module in AfterAll, causes + # PowerShell class types to get new identities, breaking type comparisons. + Import-Module -Name $script:moduleName -ErrorAction 'Stop' +} + +<# + .NOTES + This test file validates that Reporting Services sites are accessible + over HTTPS on port 443 after SSL/TLS certificate binding. It runs after + Initialize-SqlDscRS in the SSL/TLS test stage to verify the RS + configuration is complete and functional with secure connections. + + Uses explicit HTTPS URIs with port 443 to test the configured sites. +#> +Describe 'Post.Certificate.RS' { + Context 'When validating Power BI Report Server accessibility over HTTPS' -Tag @('Integration_PowerBI') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + + # Get the virtual directories + $script:reportServerVirtualDirectory = $script:configuration.VirtualDirectoryReportServer + $script:reportsVirtualDirectory = $script:configuration.VirtualDirectoryReportManager + + # Construct HTTPS URIs using port 443 + $computerName = Get-ComputerName + + $script:reportServerUri = "https://$computerName`:443/$script:reportServerVirtualDirectory" + $script:reportsUri = "https://$computerName`:443/$script:reportsVirtualDirectory" + + Write-Verbose -Message "Testing ReportServer URI: $script:reportServerUri" -Verbose + Write-Verbose -Message "Testing Reports URI: $script:reportsUri" -Verbose + } + + It 'Should have an initialized instance' { + $isInitialized = $script:configuration | Test-SqlDscRSInitialized -ErrorAction 'Stop' + + $isInitialized | Should -BeTrue + } + + It 'Should have the ReportServer site accessible over HTTPS on port 443' { + $results = Test-SqlDscRSAccessible -ReportServerUri $script:reportServerUri -Detailed -ErrorAction 'Stop' + + Write-Verbose -Message "ReportServer accessibility results: $($results | ConvertTo-Json -Compress)" -Verbose + + $results | Should -Not -BeNullOrEmpty -Because 'the command should return site accessibility results' + + # When using Uri parameter set, the site name is 'ReportServerWebService' + $siteResult = $results | Where-Object -FilterScript { $_.Site -eq 'ReportServerWebService' } + + $siteResult | Should -Not -BeNullOrEmpty -Because 'the ReportServerWebService site should have a result' + $siteResult.Accessible | Should -BeTrue -Because 'the ReportServerWebService site should be accessible over HTTPS' + $siteResult.StatusCode | Should -Be 200 -Because 'the ReportServerWebService site should return HTTP 200' + $siteResult.Uri | Should -Match '^https://' -Because 'the URI should use HTTPS protocol' + $siteResult.Uri | Should -Match ':443/' -Because 'the URI should use port 443' + } + + It 'Should have the Reports site accessible over HTTPS on port 443' { + $results = Test-SqlDscRSAccessible -ReportsUri $script:reportsUri -Detailed -ErrorAction 'Stop' + + Write-Verbose -Message "Reports accessibility results: $($results | ConvertTo-Json -Compress)" -Verbose + + $results | Should -Not -BeNullOrEmpty -Because 'the command should return site accessibility results' + + # When using Uri parameter set, the site name is 'ReportServerWebApp' + $siteResult = $results | Where-Object -FilterScript { $_.Site -eq 'ReportServerWebApp' } + + $siteResult | Should -Not -BeNullOrEmpty -Because 'the ReportServerWebApp site should have a result' + $siteResult.Accessible | Should -BeTrue -Because 'the ReportServerWebApp site should be accessible over HTTPS' + $siteResult.StatusCode | Should -Be 200 -Because 'the ReportServerWebApp site should return HTTP 200' + $siteResult.Uri | Should -Match '^https://' -Because 'the URI should use HTTPS protocol' + $siteResult.Uri | Should -Match ':443/' -Because 'the URI should use port 443' + } + + It 'Should have both sites accessible when tested together over HTTPS on port 443' { + $results = Test-SqlDscRSAccessible -ReportServerUri $script:reportServerUri -ReportsUri $script:reportsUri -Detailed -ErrorAction 'Stop' + + Write-Verbose -Message "Combined accessibility results: $($results | ConvertTo-Json -Compress)" -Verbose + + $results | Should -Not -BeNullOrEmpty -Because 'the command should return site accessibility results' + $results | Should -HaveCount 2 -Because 'we expect results for both ReportServer and Reports sites' + + foreach ($result in $results) + { + $result.Accessible | Should -BeTrue -Because "the '$($result.Site)' site should be accessible over HTTPS" + $result.StatusCode | Should -Be 200 -Because "the '$($result.Site)' site should return HTTP 200" + $result.Uri | Should -Match '^https://' -Because "the '$($result.Site)' URI should use HTTPS protocol" + $result.Uri | Should -Match ':443/' -Because "the '$($result.Site)' URI should use port 443" + } + } + } +} diff --git a/tests/Integration/Commands/Pre.Set-SqlDscRSUrlReservation.Integration.Tests.ps1 b/tests/Integration/Commands/Pre.Set-SqlDscRSUrlReservation.Integration.Tests.ps1 new file mode 100644 index 000000000..c132b7c94 --- /dev/null +++ b/tests/Integration/Commands/Pre.Set-SqlDscRSUrlReservation.Integration.Tests.ps1 @@ -0,0 +1,85 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + # Do not use -Force. Doing so, or unloading the module in AfterAll, causes + # PowerShell class types to get new identities, breaking type comparisons. + Import-Module -Name $script:moduleName -ErrorAction 'Stop' +} + +Describe 'Pre.Set-SqlDscRSUrlReservation' { + Context 'When setting HTTPS URL reservations for Power BI Report Server' -Tag @('Integration_PowerBI') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + + # Use HTTPS with port 443 for testing + $script:testUrl1 = 'https://+:443' + } + + It 'Should set HTTPS URL reservation on port 443 for ReportServerWebService' { + $script:configuration | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1 -Force -ErrorAction 'Stop' + + # Verify the URLs are set correctly + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + # Get URLs for the specified application + $currentUrls = @() + + for ($i = 0; $i -lt $reservations.Application.Count; $i++) + { + if ($reservations.Application[$i] -eq 'ReportServerWebService') + { + $currentUrls += $reservations.UrlString[$i] + } + } + + $currentUrls | Should -Contain $script:testUrl1 + } + + It 'Should set HTTPS URL reservation on port 443 for ReportServerWebApp' { + $script:configuration | Set-SqlDscRSUrlReservation -Application 'ReportServerWebApp' -UrlString $script:testUrl1 -Force -ErrorAction 'Stop' + + # Verify the URLs are set correctly + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + # Get URLs for the specified application + $currentUrls = @() + + for ($i = 0; $i -lt $reservations.Application.Count; $i++) + { + if ($reservations.Application[$i] -eq 'ReportServerWebApp') + { + $currentUrls += $reservations.UrlString[$i] + } + } + + $currentUrls | Should -Contain $script:testUrl1 + } + } +} diff --git a/tests/Integration/Commands/Remove-SqlDscRSSslCertificateBinding.Integration.Tests.ps1 b/tests/Integration/Commands/Remove-SqlDscRSSslCertificateBinding.Integration.Tests.ps1 new file mode 100644 index 000000000..beff5ff27 --- /dev/null +++ b/tests/Integration/Commands/Remove-SqlDscRSSslCertificateBinding.Integration.Tests.ps1 @@ -0,0 +1,106 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + # Do not use -Force. Doing so, or unloading the module in AfterAll, causes + # PowerShell class types to get new identities, breaking type comparisons. + Import-Module -Name $script:moduleName -ErrorAction 'Stop' + + <# + Use the same certificate and binding parameters as Add-SqlDscRSSslCertificateBinding + integration tests. This test removes the binding that was added by those tests. + #> + $script:testCertificateFriendlyName = 'SqlServerDsc SSL Integration Test Certificate' + $script:testIPAddress = '0.0.0.0' + $script:testPort = 443 +} + +<# + .NOTES + These tests remove the SSL certificate binding that was added by the + Add-SqlDscRSSslCertificateBinding integration tests. The binding is + left removed after these tests complete. +#> +Describe 'Remove-SqlDscRSSslCertificateBinding' { + BeforeAll { + # Get the test certificate that was created by Add-SqlDscRSSslCertificateBinding tests + $script:testCertificate = Get-ChildItem -Path 'Cert:\LocalMachine\My' | + Where-Object -FilterScript { $_.FriendlyName -eq $script:testCertificateFriendlyName } | + Select-Object -First 1 + + if (-not $script:testCertificate) + { + throw 'Test certificate not found. Ensure Add-SqlDscRSSslCertificateBinding integration tests have run first to create the certificate.' + } + + $script:testCertificateHash = $script:testCertificate.Thumbprint + $script:testIPAddress = '0.0.0.0' + $script:testPort = 443 + + Write-Verbose -Message ('Using self-signed certificate ''{0}'' with thumbprint ''{1}''.' -f $script:testCertificate.Subject, $script:testCertificateHash) -Verbose + } + + Context 'When removing SSL certificate binding for SQL Server Reporting Services' -Tag @('Integration_SQL2017_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should remove SSL certificate binding' { + { $script:configuration | Remove-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash $script:testCertificateHash -IPAddress $script:testIPAddress -Port $script:testPort -Force -ErrorAction 'Stop' } | Should -Not -Throw + } + } + + Context 'When removing SSL certificate binding for SQL Server Reporting Services' -Tag @('Integration_SQL2019_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should remove SSL certificate binding' { + { $script:configuration | Remove-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash $script:testCertificateHash -IPAddress $script:testIPAddress -Port $script:testPort -Force -ErrorAction 'Stop' } | Should -Not -Throw + } + } + + Context 'When removing SSL certificate binding for SQL Server Reporting Services' -Tag @('Integration_SQL2022_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should remove SSL certificate binding' { + { $script:configuration | Remove-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash $script:testCertificateHash -IPAddress $script:testIPAddress -Port $script:testPort -Force -ErrorAction 'Stop' } | Should -Not -Throw + } + } + + Context 'When removing SSL certificate binding for Power BI Report Server' -Tag @('Integration_PowerBI') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + } + + It 'Should remove SSL certificate binding' { + { $script:configuration | Remove-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash $script:testCertificateHash -IPAddress $script:testIPAddress -Port $script:testPort -Force -ErrorAction 'Stop' } | Should -Not -Throw + } + } +} diff --git a/tests/Integration/Commands/Set-SqlDscRSSslCertificateBinding.Integration.Tests.ps1 b/tests/Integration/Commands/Set-SqlDscRSSslCertificateBinding.Integration.Tests.ps1 new file mode 100644 index 000000000..f445e95f4 --- /dev/null +++ b/tests/Integration/Commands/Set-SqlDscRSSslCertificateBinding.Integration.Tests.ps1 @@ -0,0 +1,139 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + # Do not use -Force. Doing so, or unloading the module in AfterAll, causes + # PowerShell class types to get new identities, breaking type comparisons. + Import-Module -Name $script:moduleName -ErrorAction 'Stop' + + <# + Use the same certificate and binding parameters as Add-SqlDscRSSslCertificateBinding + integration tests. This test sets the binding after Remove-SqlDscRSSslCertificateBinding + has removed it. + #> + $script:testCertificateFriendlyName = 'SqlServerDsc SSL Integration Test Certificate' + $script:testIPAddress = '0.0.0.0' + $script:testPort = 443 +} + +<# + .NOTES + This test validates that Set-SqlDscRSSslCertificateBinding works correctly. + It uses the same certificate created by Add-SqlDscRSSslCertificateBinding + and sets the binding after Remove-SqlDscRSSslCertificateBinding has removed it. +#> +Describe 'Set-SqlDscRSSslCertificateBinding' { + BeforeAll { + # Get the test certificate that was created by Add-SqlDscRSSslCertificateBinding tests + $script:testCertificate = Get-ChildItem -Path 'Cert:\LocalMachine\My' | + Where-Object -FilterScript { $_.FriendlyName -eq $script:testCertificateFriendlyName } | + Select-Object -First 1 + + if (-not $script:testCertificate) + { + throw 'Test certificate not found. Ensure Add-SqlDscRSSslCertificateBinding integration tests have run first to create the certificate.' + } + + $script:testCertificateHash = $script:testCertificate.Thumbprint + $script:testIPAddress = '0.0.0.0' + $script:testPort = 443 + + Write-Verbose -Message ('Using self-signed certificate ''{0}'' with thumbprint ''{1}''.' -f $script:testCertificate.Subject, $script:testCertificateHash) -Verbose + } + + Context 'When setting SSL certificate binding for SQL Server Reporting Services' -Tag @('Integration_SQL2017_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should set SSL certificate binding' { + { $script:configuration | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash $script:testCertificateHash -IPAddress $script:testIPAddress -Port $script:testPort -Force -ErrorAction 'Stop' } | Should -Not -Throw + } + + It 'Should return configuration when using PassThru' { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + $result = $config | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash $script:testCertificateHash -IPAddress $script:testIPAddress -Port $script:testPort -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When setting SSL certificate binding for SQL Server Reporting Services' -Tag @('Integration_SQL2019_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should set SSL certificate binding' { + { $script:configuration | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash $script:testCertificateHash -IPAddress $script:testIPAddress -Port $script:testPort -Force -ErrorAction 'Stop' } | Should -Not -Throw + } + + It 'Should return configuration when using PassThru' { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + $result = $config | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash $script:testCertificateHash -IPAddress $script:testIPAddress -Port $script:testPort -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When setting SSL certificate binding for SQL Server Reporting Services' -Tag @('Integration_SQL2022_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should set SSL certificate binding' { + { $script:configuration | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash $script:testCertificateHash -IPAddress $script:testIPAddress -Port $script:testPort -Force -ErrorAction 'Stop' } | Should -Not -Throw + } + + It 'Should return configuration when using PassThru' { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + $result = $config | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash $script:testCertificateHash -IPAddress $script:testIPAddress -Port $script:testPort -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When setting SSL certificate binding for Power BI Report Server' -Tag @('Integration_PowerBI') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + } + + It 'Should set SSL certificate binding' { + { $script:configuration | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash $script:testCertificateHash -IPAddress $script:testIPAddress -Port $script:testPort -Force -ErrorAction 'Stop' } | Should -Not -Throw + } + + It 'Should return configuration when using PassThru' { + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + $result = $config | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash $script:testCertificateHash -IPAddress $script:testIPAddress -Port $script:testPort -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'PBIRS' + } + } +} diff --git a/tests/Unit/Public/Add-SqlDscRSSslCertificateBinding.Tests.ps1 b/tests/Unit/Public/Add-SqlDscRSSslCertificateBinding.Tests.ps1 new file mode 100644 index 000000000..0907b8aee --- /dev/null +++ b/tests/Unit/Public/Add-SqlDscRSSslCertificateBinding.Tests.ps1 @@ -0,0 +1,229 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Add-SqlDscRSSslCertificateBinding' { + BeforeAll { + Mock -CommandName Get-OperatingSystem -MockWith { + return [PSCustomObject] @{ + OSLanguage = 1033 + } + } + } + + Context 'When validating parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-Configuration] [-Application] [-CertificateHash] [[-IPAddress] ] [[-Port] ] [[-Lcid] ] [-PassThru] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Add-SqlDscRSSslCertificateBinding').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + } + + Context 'When adding SSL certificate binding successfully' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should add SSL certificate binding without errors' { + { $mockCimInstance | Add-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $MethodName -eq 'CreateSSLCertificateBinding' -and + $Arguments.CertificateHash -eq 'AABBCCDD' -and + $Arguments.Application -eq 'ReportServerWebService' -and + $Arguments.IPAddress -eq '0.0.0.0' -and + $Arguments.Port -eq 443 -and + $Arguments.Lcid -eq 1033 + } -Exactly -Times 1 + } + + It 'Should not return anything by default' { + $result = $mockCimInstance | Add-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -Confirm:$false + + $result | Should -BeNullOrEmpty + } + } + + Context 'When adding SSL certificate binding with custom parameters' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should use custom IP address and port' { + { $mockCimInstance | Add-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -IPAddress '192.168.1.1' -Port 8443 -Lcid 1031 -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $Arguments.IPAddress -eq '192.168.1.1' -and + $Arguments.Port -eq 8443 -and + $Arguments.Lcid -eq 1031 + } -Exactly -Times 1 + } + } + + Context 'When adding SSL certificate binding with PassThru' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should return the configuration CIM instance' { + $result = $mockCimInstance | Add-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -PassThru -Confirm:$false + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When adding SSL certificate binding with Force' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should add SSL certificate binding without confirmation' { + { $mockCimInstance | Add-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -Force } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 1 + } + } + + Context 'When CIM method fails' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + throw 'Method CreateSSLCertificateBinding() failed with an error.' + } + } + + It 'Should throw a terminating error' { + { $mockCimInstance | Add-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -Confirm:$false } | Should -Throw -ErrorId 'ASRSSCB0001,Add-SqlDscRSSslCertificateBinding' + } + } + + Context 'When using WhatIf' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should not call Invoke-RsCimMethod' { + $mockCimInstance | Add-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -WhatIf + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 0 + } + } + + Context 'When passing configuration as parameter' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should add SSL certificate binding' { + { Add-SqlDscRSSslCertificateBinding -Configuration $mockCimInstance -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 1 + } + } + + Context 'When using different application types' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should accept ReportServerWebApp application' { + { $mockCimInstance | Add-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebApp' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $Arguments.Application -eq 'ReportServerWebApp' + } -Exactly -Times 1 + } + + It 'Should accept ReportManager application' { + { $mockCimInstance | Add-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportManager' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $Arguments.Application -eq 'ReportManager' + } -Exactly -Times 1 + } + } +} diff --git a/tests/Unit/Public/Get-SqlDscRSSslCertificate.Tests.ps1 b/tests/Unit/Public/Get-SqlDscRSSslCertificate.Tests.ps1 new file mode 100644 index 000000000..9110bd639 --- /dev/null +++ b/tests/Unit/Public/Get-SqlDscRSSslCertificate.Tests.ps1 @@ -0,0 +1,196 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Get-SqlDscRSSslCertificate' { + Context 'When validating parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-Configuration] []' + } + ) { + $result = (Get-Command -Name 'Get-SqlDscRSSslCertificate').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + } + + Context 'When getting SSL certificates successfully' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + return @{ + CertName = @('Certificate1', 'Certificate2') + HostName = @('server1.domain.com', 'server2.domain.com') + CertificateHash = @('AABBCCDD', 'EEFFAABB') + Length = 2 + } + } + } + + It 'Should return SSL certificates with correct properties' { + $result = $mockCimInstance | Get-SqlDscRSSslCertificate + + $result | Should -HaveCount 2 + $result[0].CertificateName | Should -Be 'Certificate1' + $result[0].HostName | Should -Be 'server1.domain.com' + $result[0].CertificateHash | Should -Be 'AABBCCDD' + $result[1].CertificateName | Should -Be 'Certificate2' + $result[1].HostName | Should -Be 'server2.domain.com' + $result[1].CertificateHash | Should -Be 'EEFFAABB' + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $MethodName -eq 'ListSSLCertificates' -and + $null -eq $Arguments + } -Exactly -Times 1 + } + } + + Context 'When there are no SSL certificates' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + return @{ + CertName = @() + HostName = @() + CertificateHash = @() + Length = 0 + } + } + } + + It 'Should return an empty result' { + $result = $mockCimInstance | Get-SqlDscRSSslCertificate + + $result | Should -BeNullOrEmpty + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 1 + } + } + + Context 'When CIM method fails' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + throw 'Method ListSSLCertificates() failed with an error.' + } + } + + It 'Should throw a terminating error' { + { $mockCimInstance | Get-SqlDscRSSslCertificate } | Should -Throw -ErrorId 'GSRSSC0001,Get-SqlDscRSSslCertificate' + } + } + + Context 'When passing configuration as parameter' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + return @{ + CertName = @('Certificate1') + HostName = @('server1.domain.com') + CertificateHash = @('AABBCCDD') + Length = 1 + } + } + } + + It 'Should get SSL certificates' { + $result = Get-SqlDscRSSslCertificate -Configuration $mockCimInstance + + $result | Should -HaveCount 1 + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 1 + } + } + + Context 'When getting SSL certificates for Power BI Report Server' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'PBIRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + return @{ + CertName = @('PBIRS Certificate') + HostName = @('pbirs.domain.com') + CertificateHash = @('11223344') + Length = 1 + } + } + } + + It 'Should return SSL certificates without passing Lcid argument' { + $result = $mockCimInstance | Get-SqlDscRSSslCertificate + + $result | Should -HaveCount 1 + $result[0].CertificateName | Should -Be 'PBIRS Certificate' + $result[0].HostName | Should -Be 'pbirs.domain.com' + $result[0].CertificateHash | Should -Be '11223344' + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $MethodName -eq 'ListSSLCertificates' -and + $null -eq $Arguments + } -Exactly -Times 1 + } + } +} diff --git a/tests/Unit/Public/Get-SqlDscRSSslCertificateBinding.Tests.ps1 b/tests/Unit/Public/Get-SqlDscRSSslCertificateBinding.Tests.ps1 new file mode 100644 index 000000000..9675cb8d7 --- /dev/null +++ b/tests/Unit/Public/Get-SqlDscRSSslCertificateBinding.Tests.ps1 @@ -0,0 +1,174 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Get-SqlDscRSSslCertificateBinding' { + BeforeAll { + Mock -CommandName Get-OperatingSystem -MockWith { + return [PSCustomObject] @{ + OSLanguage = 1033 + } + } + } + + Context 'When validating parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-Configuration] [[-Lcid] ] []' + } + ) { + $result = (Get-Command -Name 'Get-SqlDscRSSslCertificateBinding').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + } + + Context 'When getting SSL certificate bindings successfully' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + return @{ + Application = @('ReportServerWebService', 'ReportServerWebApp') + CertificateHash = @('AABBCCDD', 'EEFFAABB') + IPAddress = @('0.0.0.0', '0.0.0.0') + Port = @(443, 443) + Lcid = @(1033, 1033) + } + } + } + + It 'Should return SSL certificate bindings' { + $result = $mockCimInstance | Get-SqlDscRSSslCertificateBinding + + $result | Should -HaveCount 2 + $result[0].Application | Should -Be 'ReportServerWebService' + $result[0].CertificateHash | Should -Be 'AABBCCDD' + $result[1].Application | Should -Be 'ReportServerWebApp' + $result[1].CertificateHash | Should -Be 'EEFFAABB' + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $MethodName -eq 'ListSSLCertificateBindings' -and + $Arguments.Lcid -eq 1033 + } -Exactly -Times 1 + } + } + + Context 'When there are no SSL certificate bindings' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + return @{ + Application = @() + CertificateHash = @() + IPAddress = @() + Port = @() + Lcid = @() + } + } + } + + It 'Should return an empty result' { + $result = $mockCimInstance | Get-SqlDscRSSslCertificateBinding + + $result | Should -BeNullOrEmpty + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 1 + } + } + + Context 'When CIM method fails' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + throw 'Method ListSSLCertificateBindings() failed with an error.' + } + } + + It 'Should throw a terminating error' { + { $mockCimInstance | Get-SqlDscRSSslCertificateBinding } | Should -Throw -ErrorId 'GSRSSCB0001,Get-SqlDscRSSslCertificateBinding' + } + } + + Context 'When passing configuration as parameter' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + return @{ + Application = @('ReportServerWebService') + CertificateHash = @('AABBCCDD') + IPAddress = @('0.0.0.0') + Port = @(443) + Lcid = @(1033) + } + } + } + + It 'Should get SSL certificate bindings' { + $result = Get-SqlDscRSSslCertificateBinding -Configuration $mockCimInstance + + $result | Should -HaveCount 1 + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 1 + } + } +} diff --git a/tests/Unit/Public/Remove-SqlDscRSSslCertificateBinding.Tests.ps1 b/tests/Unit/Public/Remove-SqlDscRSSslCertificateBinding.Tests.ps1 new file mode 100644 index 000000000..afe9262f5 --- /dev/null +++ b/tests/Unit/Public/Remove-SqlDscRSSslCertificateBinding.Tests.ps1 @@ -0,0 +1,203 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Remove-SqlDscRSSslCertificateBinding' { + BeforeAll { + Mock -CommandName Get-OperatingSystem -MockWith { + return [PSCustomObject] @{ + OSLanguage = 1033 + } + } + } + + Context 'When validating parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-Configuration] [-Application] [-CertificateHash] [[-IPAddress] ] [[-Port] ] [[-Lcid] ] [-PassThru] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Remove-SqlDscRSSslCertificateBinding').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + } + + Context 'When removing SSL certificate binding successfully' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should remove SSL certificate binding without errors' { + { $mockCimInstance | Remove-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $MethodName -eq 'RemoveSSLCertificateBindings' -and + $Arguments.CertificateHash -eq 'aabbccdd' -and + $Arguments.Application -eq 'ReportServerWebService' -and + $Arguments.IPAddress -eq '0.0.0.0' -and + $Arguments.Port -eq 443 -and + $Arguments.Lcid -eq 1033 + } -Exactly -Times 1 + } + + It 'Should not return anything by default' { + $result = $mockCimInstance | Remove-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -Confirm:$false + + $result | Should -BeNullOrEmpty + } + } + + Context 'When removing SSL certificate binding with custom parameters' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should use custom IP address and port' { + { $mockCimInstance | Remove-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -IPAddress '192.168.1.1' -Port 8443 -Lcid 1031 -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $Arguments.IPAddress -eq '192.168.1.1' -and + $Arguments.Port -eq 8443 -and + $Arguments.Lcid -eq 1031 + } -Exactly -Times 1 + } + } + + Context 'When removing SSL certificate binding with PassThru' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should return the configuration CIM instance' { + $result = $mockCimInstance | Remove-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -PassThru -Confirm:$false + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When removing SSL certificate binding with Force' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should remove SSL certificate binding without confirmation' { + { $mockCimInstance | Remove-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -Force } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 1 + } + } + + Context 'When CIM method fails' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + throw 'Method RemoveSSLCertificateBindings() failed with an error.' + } + } + + It 'Should throw a terminating error' { + { $mockCimInstance | Remove-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -Confirm:$false } | Should -Throw -ErrorId 'RSRSSCB0001,Remove-SqlDscRSSslCertificateBinding' + } + } + + Context 'When using WhatIf' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should not call Invoke-RsCimMethod' { + $mockCimInstance | Remove-SqlDscRSSslCertificateBinding -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -WhatIf + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 0 + } + } + + Context 'When passing configuration as parameter' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should remove SSL certificate binding' { + { Remove-SqlDscRSSslCertificateBinding -Configuration $mockCimInstance -CertificateHash 'AABBCCDD' -Application 'ReportServerWebService' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 1 + } + } +} diff --git a/tests/Unit/Public/Set-SqlDscRSSslCertificateBinding.Tests.ps1 b/tests/Unit/Public/Set-SqlDscRSSslCertificateBinding.Tests.ps1 new file mode 100644 index 000000000..845084dc2 --- /dev/null +++ b/tests/Unit/Public/Set-SqlDscRSSslCertificateBinding.Tests.ps1 @@ -0,0 +1,262 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies have been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies have not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.' + } +} + +BeforeAll { + $script:moduleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:moduleName -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Set-SqlDscRSSslCertificateBinding' { + BeforeAll { + Mock -CommandName Get-OperatingSystem -MockWith { + return [PSCustomObject] @{ + OSLanguage = 1033 + } + } + } + + Context 'When validating parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-Configuration] [-Application] [-CertificateHash] [[-IPAddress] ] [[-Port] ] [[-Lcid] ] [-PassThru] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Set-SqlDscRSSslCertificateBinding').ParameterSets | + Where-Object -FilterScript { $_.Name -eq $ExpectedParameterSetName } | + Select-Object -Property @( + @{ Name = 'ParameterSetName'; Expression = { $_.Name } }, + @{ Name = 'ParameterListAsString'; Expression = { $_.ToString() } } + ) + + $result.ParameterSetName | Should -Be $ExpectedParameterSetName + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + } + + Context 'When setting SSL certificate binding and no binding exists' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSSslCertificateBinding -MockWith { + return @() + } + + Mock -CommandName Add-SqlDscRSSslCertificateBinding + Mock -CommandName Remove-SqlDscRSSslCertificateBinding + } + + It 'Should add the SSL certificate binding' { + { $mockCimInstance | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'AABBCCDD' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Add-SqlDscRSSslCertificateBinding -Exactly -Times 1 + Should -Invoke -CommandName Remove-SqlDscRSSslCertificateBinding -Exactly -Times 0 + } + } + + Context 'When setting SSL certificate binding and different binding exists' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSSslCertificateBinding -MockWith { + return @( + [PSCustomObject] @{ + CertificateHash = 'oldcerthash' + Application = 'ReportServerWebService' + IPAddress = '0.0.0.0' + Port = 443 + } + ) + } + + Mock -CommandName Add-SqlDscRSSslCertificateBinding + Mock -CommandName Remove-SqlDscRSSslCertificateBinding + } + + It 'Should remove existing and add new SSL certificate binding' { + { $mockCimInstance | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'AABBCCDD' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Remove-SqlDscRSSslCertificateBinding -Exactly -Times 1 + Should -Invoke -CommandName Add-SqlDscRSSslCertificateBinding -Exactly -Times 1 + } + } + + Context 'When binding is already in desired state' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSSslCertificateBinding -MockWith { + return @( + [PSCustomObject] @{ + CertificateHash = 'aabbccdd' + Application = 'ReportServerWebService' + IPAddress = '0.0.0.0' + Port = 443 + } + ) + } + + Mock -CommandName Add-SqlDscRSSslCertificateBinding + Mock -CommandName Remove-SqlDscRSSslCertificateBinding + } + + It 'Should not add or remove any bindings' { + { $mockCimInstance | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'AABBCCDD' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Add-SqlDscRSSslCertificateBinding -Exactly -Times 0 + Should -Invoke -CommandName Remove-SqlDscRSSslCertificateBinding -Exactly -Times 0 + } + } + + Context 'When using PassThru' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSSslCertificateBinding -MockWith { + return @() + } + + Mock -CommandName Add-SqlDscRSSslCertificateBinding + Mock -CommandName Remove-SqlDscRSSslCertificateBinding + } + + It 'Should return the configuration CIM instance' { + $result = $mockCimInstance | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'AABBCCDD' -PassThru -Confirm:$false + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When using Force' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSSslCertificateBinding -MockWith { + return @() + } + + Mock -CommandName Add-SqlDscRSSslCertificateBinding + Mock -CommandName Remove-SqlDscRSSslCertificateBinding + } + + It 'Should set SSL certificate binding without confirmation' { + { $mockCimInstance | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'AABBCCDD' -Force } | Should -Not -Throw + + Should -Invoke -CommandName Add-SqlDscRSSslCertificateBinding -Exactly -Times 1 + } + } + + Context 'When using WhatIf' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSSslCertificateBinding -MockWith { + return @() + } + + Mock -CommandName Add-SqlDscRSSslCertificateBinding + Mock -CommandName Remove-SqlDscRSSslCertificateBinding + } + + It 'Should not add or remove any bindings' { + $mockCimInstance | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'AABBCCDD' -WhatIf + + Should -Invoke -CommandName Add-SqlDscRSSslCertificateBinding -Exactly -Times 0 + Should -Invoke -CommandName Remove-SqlDscRSSslCertificateBinding -Exactly -Times 0 + } + } + + Context 'When passing configuration as parameter' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSSslCertificateBinding -MockWith { + return @() + } + + Mock -CommandName Add-SqlDscRSSslCertificateBinding + Mock -CommandName Remove-SqlDscRSSslCertificateBinding + } + + It 'Should set SSL certificate binding' { + { Set-SqlDscRSSslCertificateBinding -Configuration $mockCimInstance -Application 'ReportServerWebService' -CertificateHash 'AABBCCDD' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Get-SqlDscRSSslCertificateBinding -Exactly -Times 1 + } + } + + Context 'When using custom IP address and port' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSSslCertificateBinding -MockWith { + return @() + } + + Mock -CommandName Add-SqlDscRSSslCertificateBinding + Mock -CommandName Remove-SqlDscRSSslCertificateBinding + } + + It 'Should use custom IP address and port' { + { $mockCimInstance | Set-SqlDscRSSslCertificateBinding -Application 'ReportServerWebService' -CertificateHash 'AABBCCDD' -IPAddress '192.168.1.1' -Port 8443 -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Add-SqlDscRSSslCertificateBinding -ParameterFilter { + $IPAddress -eq '192.168.1.1' -and $Port -eq 8443 + } -Exactly -Times 1 + } + } +}