From 54da354ce1a27ae48eaaef47af7575efea6a9104 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 11:04:58 +0100 Subject: [PATCH 01/14] Add functions to manage URL reservations for Reporting Services --- CHANGELOG.md | 20 ++ azure-pipelines.yml | 6 + source/Private/Get-OperatingSystem.ps1 | 49 ++++ source/Public/Add-SqlDscRSUrlReservation.ps1 | 173 ++++++++++++ source/Public/Get-SqlDscRSUrlReservation.ps1 | 90 +++++++ .../Public/Remove-SqlDscRSUrlReservation.ps1 | 173 ++++++++++++ source/en-US/SqlServerDsc.strings.psd1 | 24 ++ ...lDscRSUrlReservation.Integration.Tests.ps1 | 246 +++++++++++++++++ ...lDscRSUrlReservation.Integration.Tests.ps1 | 139 ++++++++++ tests/Integration/Commands/README.md | 12 + ...lDscRSUrlReservation.Integration.Tests.ps1 | 227 ++++++++++++++++ .../Private/Get-OperatingSystem.Tests.ps1 | 121 +++++++++ .../Add-SqlDscRSUrlReservation.Tests.ps1 | 247 ++++++++++++++++++ .../Get-SqlDscRSUrlReservation.Tests.ps1 | 140 ++++++++++ .../Remove-SqlDscRSUrlReservation.Tests.ps1 | 247 ++++++++++++++++++ 15 files changed, 1914 insertions(+) create mode 100644 source/Private/Get-OperatingSystem.ps1 create mode 100644 source/Public/Add-SqlDscRSUrlReservation.ps1 create mode 100644 source/Public/Get-SqlDscRSUrlReservation.ps1 create mode 100644 source/Public/Remove-SqlDscRSUrlReservation.ps1 create mode 100644 tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1 create mode 100644 tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1 create mode 100644 tests/Integration/Commands/Remove-SqlDscRSUrlReservation.Integration.Tests.ps1 create mode 100644 tests/Unit/Private/Get-OperatingSystem.Tests.ps1 create mode 100644 tests/Unit/Public/Add-SqlDscRSUrlReservation.Tests.ps1 create mode 100644 tests/Unit/Public/Get-SqlDscRSUrlReservation.Tests.ps1 create mode 100644 tests/Unit/Public/Remove-SqlDscRSUrlReservation.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index bdb5444a11..36a6d3ae6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added public command `Get-SqlDscRSUrlReservation` to retrieve URL reservations + for SQL Server Reporting Services or Power BI Report Server by calling the + `ListReservedUrls` CIM method. Returns the raw CIM method result containing + Application and UrlString arrays + ([issue #2016](https://github.com/dsccommunity/SqlServerDsc/issues/2016)). +- Added public command `Add-SqlDscRSUrlReservation` to add URL reservations for + SQL Server Reporting Services or Power BI Report Server by calling the + `ReserveUrl` CIM method. Supports ReportServerWebService, ReportServerWebApp, + and ReportManager application types. Accepts pipeline input, supports + `-WhatIf`/`-Confirm`, `-PassThru`, and `-Force` + ([issue #2016](https://github.com/dsccommunity/SqlServerDsc/issues/2016)). +- Added public command `Remove-SqlDscRSUrlReservation` to remove URL reservations + for SQL Server Reporting Services or Power BI Report Server by calling the + `RemoveURL` CIM method. Supports ReportServerWebService, ReportServerWebApp, + and ReportManager application types. Accepts pipeline input, supports + `-WhatIf`/`-Confirm`, `-PassThru`, and `-Force` + ([issue #2024](https://github.com/dsccommunity/SqlServerDsc/issues/2024)). +- Added private function `Get-OperatingSystem` to retrieve operating system + information via the Win32_OperatingSystem CIM class. Used to get the OS + language code (OSLanguage) for URL reservation operations. - Added public command `Get-SqlDscRSConfiguration` to retrieve the `MSReportServer_ConfigurationSetting` CIM instance for SQL Server Reporting Services or Power BI Report Server. Supports auto-detection of the Reporting diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1a9858e375..711a5b52e1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -539,6 +539,9 @@ stages: 'tests/Integration/Commands/Get-SqlDscRSConfiguration.Integration.Tests.ps1' 'tests/Integration/Commands/Enable-SqlDscRsSecureConnection.Integration.Tests.ps1' 'tests/Integration/Commands/Disable-SqlDscRsSecureConnection.Integration.Tests.ps1' + 'tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1' + 'tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1' + 'tests/Integration/Commands/Remove-SqlDscRSUrlReservation.Integration.Tests.ps1' # Group 8 'tests/Integration/Commands/Repair-SqlDscReportingService.Integration.Tests.ps1' # Group 9 @@ -609,6 +612,9 @@ stages: 'tests/Integration/Commands/Get-SqlDscRSConfiguration.Integration.Tests.ps1' 'tests/Integration/Commands/Enable-SqlDscRsSecureConnection.Integration.Tests.ps1' 'tests/Integration/Commands/Disable-SqlDscRsSecureConnection.Integration.Tests.ps1' + 'tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1' + 'tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1' + 'tests/Integration/Commands/Remove-SqlDscRSUrlReservation.Integration.Tests.ps1' # Group 8 'tests/Integration/Commands/Repair-SqlDscPowerBIReportServer.Integration.Tests.ps1' # Group 9 diff --git a/source/Private/Get-OperatingSystem.ps1 b/source/Private/Get-OperatingSystem.ps1 new file mode 100644 index 0000000000..4af976719d --- /dev/null +++ b/source/Private/Get-OperatingSystem.ps1 @@ -0,0 +1,49 @@ +<# + .SYNOPSIS + Gets the operating system CIM instance. + + .DESCRIPTION + Gets the operating system CIM instance from Win32_OperatingSystem class. + This function is used to retrieve operating system information such as + the OS language (OSLanguage) which is needed for Reporting Services + URL reservation operations. + + .EXAMPLE + Get-OperatingSystem + + Returns the Win32_OperatingSystem CIM instance. + + .EXAMPLE + (Get-OperatingSystem).OSLanguage + + Returns the operating system language code (e.g., 1033 for English). + + .INPUTS + None. + + .OUTPUTS + `Microsoft.Management.Infrastructure.CimInstance` + + Returns the Win32_OperatingSystem CIM instance. +#> +function Get-OperatingSystem +{ + [CmdletBinding()] + [OutputType([Microsoft.Management.Infrastructure.CimInstance])] + param () + + Write-Verbose -Message $script:localizedData.Get_OperatingSystem_Getting + + $wmiOperatingSystem = Get-CimInstance -ClassName 'Win32_OperatingSystem' -Namespace 'root/cimv2' -ErrorAction 'SilentlyContinue' + + if ($null -eq $wmiOperatingSystem) + { + $errorMessage = $script:localizedData.Get_OperatingSystem_FailedToGet + + $errorRecord = New-ErrorRecord -Exception (New-InvalidOperationException -Message $errorMessage -PassThru) -ErrorId 'GOS0001' -ErrorCategory 'ObjectNotFound' -TargetObject $null + + $PSCmdlet.ThrowTerminatingError($errorRecord) + } + + return $wmiOperatingSystem +} diff --git a/source/Public/Add-SqlDscRSUrlReservation.ps1 b/source/Public/Add-SqlDscRSUrlReservation.ps1 new file mode 100644 index 0000000000..9bb2b940ff --- /dev/null +++ b/source/Public/Add-SqlDscRSUrlReservation.ps1 @@ -0,0 +1,173 @@ +<# + .SYNOPSIS + Adds a URL reservation for SQL Server Reporting Services. + + .DESCRIPTION + Adds a URL reservation for SQL Server Reporting Services or + Power BI Report Server by calling the `ReserveUrl` method on + the `MSReportServer_ConfigurationSetting` CIM instance. + + This command reserves a URL for a specific application in the + Reporting Services instance. The application can be the Report Server + Web Service, the Reports web application (SQL Server 2016+), or the + Report Manager (SQL Server 2014 and earlier). + + 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 reserve the URL. 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 UrlString + Specifies the URL string to reserve. The URL string format is typically + 'http://+:80' or 'https://+:443' where the plus sign (+) is a wildcard + that matches all hostnames. + + .PARAMETER Lcid + Specifies the language code identifier (LCID) for the URL reservation. + 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 URL reservation. + + .PARAMETER Force + If specified, suppresses the confirmation prompt. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' + + Adds a URL reservation for the Report Server Web Service on port 80. + + .EXAMPLE + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + Add-SqlDscRSUrlReservation -Configuration $config -Application 'ReportServerWebApp' -UrlString 'https://+:443' -Confirm:$false + + Adds a URL reservation for the Reports web application on port 443 + without confirmation. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:8080' -Lcid 1033 -PassThru + + Adds a URL reservation with a specific LCID 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-reserveurl +#> +function Add-SqlDscRSUrlReservation +{ + # cSpell: ignore PBIRS + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] + [OutputType()] + [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] + $UrlString, + + [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_SqlDscRSUrlReservation_Adding -f $UrlString, $Application, $instanceName) + + $descriptionMessage = $script:localizedData.Add_SqlDscRSUrlReservation_ShouldProcessDescription -f $UrlString, $Application, $instanceName + $confirmationMessage = $script:localizedData.Add_SqlDscRSUrlReservation_ShouldProcessConfirmation -f $UrlString, $Application + $captionMessage = $script:localizedData.Add_SqlDscRSUrlReservation_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) + { + $invokeRsCimMethodParameters = @{ + CimInstance = $Configuration + MethodName = 'ReserveUrl' + Arguments = @{ + Application = $Application + UrlString = $UrlString + Lcid = $Lcid + } + } + + try + { + $null = Invoke-RsCimMethod @invokeRsCimMethodParameters + } + catch + { + $errorMessage = $script:localizedData.Add_SqlDscRSUrlReservation_FailedToAdd -f $instanceName, $_.Exception.Message + + $errorRecord = New-ErrorRecord -Exception (New-InvalidOperationException -Message $errorMessage -PassThru) -ErrorId 'ASRUR0001' -ErrorCategory 'InvalidOperation' -TargetObject $Configuration + + $PSCmdlet.ThrowTerminatingError($errorRecord) + } + } + + if ($PassThru.IsPresent) + { + return $Configuration + } + } +} diff --git a/source/Public/Get-SqlDscRSUrlReservation.ps1 b/source/Public/Get-SqlDscRSUrlReservation.ps1 new file mode 100644 index 0000000000..90f9c9f030 --- /dev/null +++ b/source/Public/Get-SqlDscRSUrlReservation.ps1 @@ -0,0 +1,90 @@ +<# + .SYNOPSIS + Gets the URL reservations for SQL Server Reporting Services. + + .DESCRIPTION + Gets the URL reservations for SQL Server Reporting Services or + Power BI Report Server by calling the `ListReservedUrls` method on + the `MSReportServer_ConfigurationSetting` CIM 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. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Get-SqlDscRSUrlReservation + + Gets all URL reservations for the SSRS instance. + + .EXAMPLE + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + Get-SqlDscRSUrlReservation -Configuration $config + + Gets all URL reservations for the SSRS instance by passing the + configuration as a parameter. + + .INPUTS + `Microsoft.Management.Infrastructure.CimInstance` + + Accepts MSReportServer_ConfigurationSetting CIM instance via pipeline. + + .OUTPUTS + `Microsoft.Management.Infrastructure.CimMethodResult` + + Returns the raw CIM method result containing Application and UrlString + arrays with the reserved URL information. + + .NOTES + The returned object contains two arrays: + - Application: Array of application names (e.g., 'ReportServerWebService', + 'ReportServerWebApp', 'ReportManager') + - UrlString: Array of URL strings (e.g., 'http://+:80') + + The arrays are correlated by index, so Application[0] corresponds to + UrlString[0]. +#> +function Get-SqlDscRSUrlReservation +{ + # cSpell: ignore PBIRS + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] + [CmdletBinding()] + [OutputType([Microsoft.Management.Infrastructure.CimMethodResult])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Object] + $Configuration + ) + + process + { + $instanceName = $Configuration.InstanceName + + Write-Verbose -Message ($script:localizedData.Get_SqlDscRSUrlReservation_Getting -f $instanceName) + + $invokeRsCimMethodParameters = @{ + CimInstance = $Configuration + MethodName = 'ListReservedUrls' + } + + try + { + $result = Invoke-RsCimMethod @invokeRsCimMethodParameters + } + catch + { + $errorMessage = $script:localizedData.Get_SqlDscRSUrlReservation_FailedToGet -f $instanceName, $_.Exception.Message + + $errorRecord = New-ErrorRecord -Exception (New-InvalidOperationException -Message $errorMessage -PassThru) -ErrorId 'GSRUR0001' -ErrorCategory 'InvalidOperation' -TargetObject $Configuration + + $PSCmdlet.ThrowTerminatingError($errorRecord) + } + + return $result + } +} diff --git a/source/Public/Remove-SqlDscRSUrlReservation.ps1 b/source/Public/Remove-SqlDscRSUrlReservation.ps1 new file mode 100644 index 0000000000..0c4f47f5fb --- /dev/null +++ b/source/Public/Remove-SqlDscRSUrlReservation.ps1 @@ -0,0 +1,173 @@ +<# + .SYNOPSIS + Removes a URL reservation for SQL Server Reporting Services. + + .DESCRIPTION + Removes a URL reservation for SQL Server Reporting Services or + Power BI Report Server by calling the `RemoveURL` method on + the `MSReportServer_ConfigurationSetting` CIM instance. + + This command removes a URL reservation for a specific application in the + Reporting Services instance. The application can be the Report Server + Web Service, the Reports web application (SQL Server 2016+), or the + Report Manager (SQL Server 2014 and earlier). + + 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 remove the URL reservation. + 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 UrlString + Specifies the URL string to remove. The URL string format is typically + 'http://+:80' or 'https://+:443' where the plus sign (+) is a wildcard + that matches all hostnames. + + .PARAMETER Lcid + Specifies the language code identifier (LCID) for the URL reservation. + 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 URL reservation. + + .PARAMETER Force + If specified, suppresses the confirmation prompt. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' + + Removes the URL reservation for the Report Server Web Service on port 80. + + .EXAMPLE + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + Remove-SqlDscRSUrlReservation -Configuration $config -Application 'ReportServerWebApp' -UrlString 'https://+:443' -Confirm:$false + + Removes the URL reservation for the Reports web application on port 443 + without confirmation. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:8080' -Force -PassThru + + Removes a URL reservation 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-removeurl +#> +function Remove-SqlDscRSUrlReservation +{ + # cSpell: ignore PBIRS + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [OutputType()] + [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] + $UrlString, + + [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_SqlDscRSUrlReservation_Removing -f $UrlString, $Application, $instanceName) + + $descriptionMessage = $script:localizedData.Remove_SqlDscRSUrlReservation_ShouldProcessDescription -f $UrlString, $Application, $instanceName + $confirmationMessage = $script:localizedData.Remove_SqlDscRSUrlReservation_ShouldProcessConfirmation -f $UrlString, $Application + $captionMessage = $script:localizedData.Remove_SqlDscRSUrlReservation_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) + { + $invokeRsCimMethodParameters = @{ + CimInstance = $Configuration + MethodName = 'RemoveURL' + Arguments = @{ + Application = $Application + UrlString = $UrlString + Lcid = $Lcid + } + } + + try + { + $null = Invoke-RsCimMethod @invokeRsCimMethodParameters + } + catch + { + $errorMessage = $script:localizedData.Remove_SqlDscRSUrlReservation_FailedToRemove -f $instanceName, $_.Exception.Message + + $errorRecord = New-ErrorRecord -Exception (New-InvalidOperationException -Message $errorMessage -PassThru) -ErrorId 'RSRUR0001' -ErrorCategory 'InvalidOperation' -TargetObject $Configuration + + $PSCmdlet.ThrowTerminatingError($errorRecord) + } + } + + if ($PassThru.IsPresent) + { + return $Configuration + } + } +} diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 8a30d40e58..5e72fe4c05 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -706,4 +706,28 @@ ConvertFrom-StringData @' Get_SqlDscRSPackage_GettingVersionFromFile = Getting version information from file '{0}'. (GSDRSP0001) Get_SqlDscRSPackage_InvalidProductName = The product name '{0}' is not a valid Reporting Services package. Expected product names are: '{1}'. Use the Force parameter to skip this validation. (GSDRSP0002) Get_SqlDscRSPackage_ReturningVersionInfo = Returning version information for '{0}' version '{1}'. (GSDRSP0003) + + ## Get-OperatingSystem + Get_OperatingSystem_Getting = Getting operating system information. + Get_OperatingSystem_FailedToGet = Failed to get operating system information. Unable to find WMI object Win32_OperatingSystem. (GOS0001) + + ## Get-SqlDscRSUrlReservation + Get_SqlDscRSUrlReservation_Getting = Getting URL reservations for Reporting Services instance '{0}'. + Get_SqlDscRSUrlReservation_FailedToGet = Failed to get URL reservations for Reporting Services instance '{0}'. {1} (GSRUR0001) + + ## Add-SqlDscRSUrlReservation + Add_SqlDscRSUrlReservation_Adding = Adding URL reservation '{0}' for application '{1}' on Reporting Services instance '{2}'. + Add_SqlDscRSUrlReservation_ShouldProcessDescription = Adding URL reservation '{0}' for application '{1}' on Reporting Services instance '{2}'. + Add_SqlDscRSUrlReservation_ShouldProcessConfirmation = Are you sure you want to add URL reservation '{0}' for application '{1}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Add_SqlDscRSUrlReservation_ShouldProcessCaption = Add URL reservation for Reporting Services instance + Add_SqlDscRSUrlReservation_FailedToAdd = Failed to add URL reservation for Reporting Services instance '{0}'. {1} (ASRUR0001) + + ## Remove-SqlDscRSUrlReservation + Remove_SqlDscRSUrlReservation_Removing = Removing URL reservation '{0}' for application '{1}' on Reporting Services instance '{2}'. + Remove_SqlDscRSUrlReservation_ShouldProcessDescription = Removing URL reservation '{0}' for application '{1}' on Reporting Services instance '{2}'. + Remove_SqlDscRSUrlReservation_ShouldProcessConfirmation = Are you sure you want to remove URL reservation '{0}' for application '{1}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Remove_SqlDscRSUrlReservation_ShouldProcessCaption = Remove URL reservation for Reporting Services instance + Remove_SqlDscRSUrlReservation_FailedToRemove = Failed to remove URL reservation for Reporting Services instance '{0}'. {1} (RSRUR0001) '@ diff --git a/tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1 new file mode 100644 index 0000000000..e091403e18 --- /dev/null +++ b/tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1 @@ -0,0 +1,246 @@ +[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 'Add-SqlDscRSUrlReservation' { + Context 'When adding URL reservation for SQL Server Reporting Services' -Tag @('Integration_SQL2017_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + + # Store original URL reservations to restore later + $script:originalReservations = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + # Use a unique port for testing to avoid conflicts + $script:testPort = 18080 + $script:testUrl = "http://+:$script:testPort" + } + + AfterAll { + # Clean up: remove the test URL reservation if it was added + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore errors during cleanup + } + } + + It 'Should add URL reservation using pipeline' { + { $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' } | Should -Not -Throw + + # Verify the URL was added + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + $reservations.UrlReservations | Should -Contain $script:testUrl + } + + It 'Should return configuration when using PassThru' { + # First remove the test URL if it exists + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore + } + + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $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') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + + # Use a unique port for testing to avoid conflicts + $script:testPort = 18080 + $script:testUrl = "http://+:$script:testPort" + } + + AfterAll { + # Clean up: remove the test URL reservation if it was added + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore errors during cleanup + } + } + + It 'Should add URL reservation using pipeline' { + { $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' } | Should -Not -Throw + + # Verify the URL was added + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + $reservations.UrlReservations | Should -Contain $script:testUrl + } + + It 'Should return configuration when using PassThru' { + # First remove the test URL if it exists + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore + } + + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $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') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + + # Use a unique port for testing to avoid conflicts + $script:testPort = 18080 + $script:testUrl = "http://+:$script:testPort" + } + + AfterAll { + # Clean up: remove the test URL reservation if it was added + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore errors during cleanup + } + } + + It 'Should add URL reservation using pipeline' { + { $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' } | Should -Not -Throw + + # Verify the URL was added + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + $reservations.UrlReservations | Should -Contain $script:testUrl + } + + It 'Should return configuration when using PassThru' { + # First remove the test URL if it exists + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore + } + + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $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') { + # cSpell: ignore PBIRS + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + + # Use a unique port for testing to avoid conflicts + $script:testPort = 18080 + $script:testUrl = "http://+:$script:testPort" + } + + AfterAll { + # Clean up: remove the test URL reservation if it was added + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore errors during cleanup + } + } + + It 'Should add URL reservation for PBIRS using pipeline' { + { $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' } | Should -Not -Throw + + # Verify the URL was added + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + $reservations.UrlReservations | Should -Contain $script:testUrl + } + + It 'Should return configuration when using PassThru' { + # First remove the test URL if it exists + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore + } + + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $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-SqlDscRSUrlReservation.Integration.Tests.ps1 b/tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1 new file mode 100644 index 0000000000..379da0035e --- /dev/null +++ b/tests/Integration/Commands/Get-SqlDscRSUrlReservation.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' +} + +Describe 'Get-SqlDscRSUrlReservation' { + Context 'When getting URL reservations for SQL Server Reporting Services' -Tag @('Integration_SQL2017_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + } + + It 'Should return URL reservations using pipeline' { + $result = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return URL reservations using Configuration parameter' { + $result = Get-SqlDscRSUrlReservation -Configuration $script:configuration -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return result with expected properties' { + $result = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + # The result should be a CIM method result with URL reservation properties + $result | Should -Not -BeNullOrEmpty + $result.HRESULT | Should -Be 0 + } + } + + Context 'When getting URL reservations for SQL Server Reporting Services' -Tag @('Integration_SQL2019_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + } + + It 'Should return URL reservations using pipeline' { + $result = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return URL reservations using Configuration parameter' { + $result = Get-SqlDscRSUrlReservation -Configuration $script:configuration -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return result with expected properties' { + $result = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + # The result should be a CIM method result with URL reservation properties + $result | Should -Not -BeNullOrEmpty + $result.HRESULT | Should -Be 0 + } + } + + Context 'When getting URL reservations for SQL Server Reporting Services' -Tag @('Integration_SQL2022_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + } + + It 'Should return URL reservations using pipeline' { + $result = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return URL reservations using Configuration parameter' { + $result = Get-SqlDscRSUrlReservation -Configuration $script:configuration -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return result with expected properties' { + $result = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + # The result should be a CIM method result with URL reservation properties + $result | Should -Not -BeNullOrEmpty + $result.HRESULT | Should -Be 0 + } + } + + Context 'When getting URL reservations for Power BI Report Server' -Tag @('Integration_PowerBI') { + # cSpell: ignore PBIRS + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + } + + It 'Should return URL reservations using pipeline' { + $result = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return URL reservations using Configuration parameter' { + $result = Get-SqlDscRSUrlReservation -Configuration $script:configuration -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + } + + It 'Should return result with expected properties' { + $result = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + # The result should be a CIM method result with URL reservation properties + $result | Should -Not -BeNullOrEmpty + $result.HRESULT | Should -Be 0 + } + } +} diff --git a/tests/Integration/Commands/README.md b/tests/Integration/Commands/README.md index 71adea6370..20843c14f1 100644 --- a/tests/Integration/Commands/README.md +++ b/tests/Integration/Commands/README.md @@ -165,6 +165,12 @@ Get-SqlDscInstalledInstance | 2 | 1 (Install-SqlDscReportingService), 0 (Prerequ Get-SqlDscRSPackage | 2 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - Get-SqlDscRSSetupConfiguration | 2 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - Test-SqlDscRSInstalled | 2 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - +Get-SqlDscRSConfiguration | 3 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - +Enable-SqlDscRsSecureConnection | 3 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - +Disable-SqlDscRsSecureConnection | 3 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - +Get-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - +Add-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - +Remove-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - Repair-SqlDscReportingService | 8 | 1 (Install-SqlDscReportingService) | SSRS | - Uninstall-SqlDscReportingService | 9 | 8 (Repair-SqlDscReportingService) | - | - @@ -184,6 +190,12 @@ Get-SqlDscInstalledInstance | 2 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prer Get-SqlDscRSPackage | 2 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - Get-SqlDscRSSetupConfiguration | 2 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - Test-SqlDscRSInstalled | 2 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - +Get-SqlDscRSConfiguration | 3 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - +Enable-SqlDscRsSecureConnection | 3 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - +Disable-SqlDscRsSecureConnection | 3 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - +Get-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - +Add-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - +Remove-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - Repair-SqlDscPowerBIReportServer | 8 | 1 (Install-SqlDscPowerBIReportServer) | PBIRS | - Uninstall-SqlDscPowerBIReportServer | 9 | 8 (Repair-SqlDscPowerBIReportServer) | - | - diff --git a/tests/Integration/Commands/Remove-SqlDscRSUrlReservation.Integration.Tests.ps1 b/tests/Integration/Commands/Remove-SqlDscRSUrlReservation.Integration.Tests.ps1 new file mode 100644 index 0000000000..16fa1d0d25 --- /dev/null +++ b/tests/Integration/Commands/Remove-SqlDscRSUrlReservation.Integration.Tests.ps1 @@ -0,0 +1,227 @@ +[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 'Remove-SqlDscRSUrlReservation' { + Context 'When removing URL reservation for SQL Server Reporting Services' -Tag @('Integration_SQL2017_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + + # Use a unique port for testing to avoid conflicts + $script:testPort = 18081 + $script:testUrl = "http://+:$script:testPort" + + # Add a test URL reservation to remove + $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' + } + + AfterAll { + # Clean up: ensure the test URL reservation is removed + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore errors during cleanup + } + } + + It 'Should remove URL reservation using pipeline' { + { $script:configuration | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' } | Should -Not -Throw + + # Verify the URL was removed + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + $reservations.UrlReservations | Should -Not -Contain $script:testUrl + } + + It 'Should return configuration when using PassThru' { + # First add the test URL back + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' + + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $result = $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When removing URL reservation for SQL Server Reporting Services' -Tag @('Integration_SQL2019_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + + # Use a unique port for testing to avoid conflicts + $script:testPort = 18081 + $script:testUrl = "http://+:$script:testPort" + + # Add a test URL reservation to remove + $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' + } + + AfterAll { + # Clean up: ensure the test URL reservation is removed + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore errors during cleanup + } + } + + It 'Should remove URL reservation using pipeline' { + { $script:configuration | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' } | Should -Not -Throw + + # Verify the URL was removed + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + $reservations.UrlReservations | Should -Not -Contain $script:testUrl + } + + It 'Should return configuration when using PassThru' { + # First add the test URL back + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' + + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $result = $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When removing URL reservation for SQL Server Reporting Services' -Tag @('Integration_SQL2022_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + + # Use a unique port for testing to avoid conflicts + $script:testPort = 18081 + $script:testUrl = "http://+:$script:testPort" + + # Add a test URL reservation to remove + $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' + } + + AfterAll { + # Clean up: ensure the test URL reservation is removed + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore errors during cleanup + } + } + + It 'Should remove URL reservation using pipeline' { + { $script:configuration | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' } | Should -Not -Throw + + # Verify the URL was removed + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + $reservations.UrlReservations | Should -Not -Contain $script:testUrl + } + + It 'Should return configuration when using PassThru' { + # First add the test URL back + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' + + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $result = $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When removing URL reservation for Power BI Report Server' -Tag @('Integration_PowerBI') { + # cSpell: ignore PBIRS + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + + # Use a unique port for testing to avoid conflicts + $script:testPort = 18081 + $script:testUrl = "http://+:$script:testPort" + + # Add a test URL reservation to remove + $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' + } + + AfterAll { + # Clean up: ensure the test URL reservation is removed + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' + } + catch + { + # Ignore errors during cleanup + } + } + + It 'Should remove URL reservation for PBIRS using pipeline' { + { $script:configuration | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' } | Should -Not -Throw + + # Verify the URL was removed + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + $reservations.UrlReservations | Should -Not -Contain $script:testUrl + } + + It 'Should return configuration when using PassThru' { + # First add the test URL back + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' + + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $result = $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'PBIRS' + } + } +} diff --git a/tests/Unit/Private/Get-OperatingSystem.Tests.ps1 b/tests/Unit/Private/Get-OperatingSystem.Tests.ps1 new file mode 100644 index 0000000000..f04b6d608e --- /dev/null +++ b/tests/Unit/Private/Get-OperatingSystem.Tests.ps1 @@ -0,0 +1,121 @@ +[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' + + Import-Module -Name $script:moduleName -ErrorAction 'Stop' + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName + + $env:SqlServerDscCI = $true + + InModuleScope -ScriptBlock { + <# + Stub for Get-CimInstance since it doesn't exist on macOS and + we need to be able to mock it. + #> + function script:Get-CimInstance + { + param + ( + [System.String] + $ClassName, + + [System.String] + $Namespace, + + [System.String] + $ErrorAction + ) + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + 'StubNotImplemented', + 'StubCalledError', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $MyInvocation.MyCommand + ) + ) + } + } +} + +AfterAll { + $env:SqlServerDscCI = $null + + InModuleScope -ScriptBlock { + Remove-Item -Path 'function:script:Get-CimInstance' -Force -ErrorAction SilentlyContinue + } + + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') +} + +Describe 'Get-OperatingSystem' -Tag 'Private' { + Context 'When getting the operating system successfully' { + BeforeAll { + Mock -CommandName Get-CimInstance -MockWith { + return [PSCustomObject] @{ + OSLanguage = 1033 + Caption = 'Microsoft Windows Server 2022' + OSArchitecture = '64-bit' + } + } + } + + It 'Should return the operating system CIM instance' { + InModuleScope -ScriptBlock { + $result = Get-OperatingSystem + + $result | Should -Not -BeNullOrEmpty + $result.OSLanguage | Should -Be 1033 + $result.Caption | Should -Be 'Microsoft Windows Server 2022' + } + + Should -Invoke -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_OperatingSystem' -and + $Namespace -eq 'root/cimv2' + } -Exactly -Times 1 + } + } + + Context 'When failing to get the operating system' { + BeforeAll { + Mock -CommandName Get-CimInstance -MockWith { + return $null + } + } + + It 'Should throw a terminating error' { + InModuleScope -ScriptBlock { + { Get-OperatingSystem } | Should -Throw -ErrorId 'GOS0001,Get-OperatingSystem' + } + } + } +} diff --git a/tests/Unit/Public/Add-SqlDscRSUrlReservation.Tests.ps1 b/tests/Unit/Public/Add-SqlDscRSUrlReservation.Tests.ps1 new file mode 100644 index 0000000000..c5c7c11155 --- /dev/null +++ b/tests/Unit/Public/Add-SqlDscRSUrlReservation.Tests.ps1 @@ -0,0 +1,247 @@ +[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-SqlDscRSUrlReservation' { + 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] [-UrlString] [[-Lcid] ] [-PassThru] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Add-SqlDscRSUrlReservation').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 URL reservation successfully' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should add URL reservation without errors' { + { $mockCimInstance | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $MethodName -eq 'ReserveUrl' -and + $Arguments.Application -eq 'ReportServerWebService' -and + $Arguments.UrlString -eq 'http://+:80' -and + $Arguments.Lcid -eq 1033 + } -Exactly -Times 1 + } + + It 'Should not return anything by default' { + $result = $mockCimInstance | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Confirm:$false + + $result | Should -BeNullOrEmpty + } + } + + Context 'When adding URL reservation with PassThru' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should return the configuration CIM instance' { + $result = $mockCimInstance | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -PassThru -Confirm:$false + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When adding URL reservation with Force' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should add URL reservation without confirmation' { + { $mockCimInstance | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Force } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 1 + } + } + + Context 'When adding URL reservation with custom Lcid' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should use the specified Lcid' { + { $mockCimInstance | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Lcid 1031 -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $Arguments.Lcid -eq 1031 + } -Exactly -Times 1 + } + + It 'Should not call Get-OperatingSystem when Lcid is specified' { + $mockCimInstance | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Lcid 1031 -Confirm:$false + + Should -Invoke -CommandName Get-OperatingSystem -Exactly -Times 0 + } + } + + Context 'When CIM method fails with ExtendedErrors' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + throw 'Method ReserveUrl() failed with an error. Error: Access denied;Permission error (HRESULT:-2147024891)' + } + } + + It 'Should throw a terminating error' { + { $mockCimInstance | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Confirm:$false } | Should -Throw -ErrorId 'ASRUR0001,Add-SqlDscRSUrlReservation' + } + } + + Context 'When CIM method fails with Error property' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + throw 'Method ReserveUrl() failed with an error. Error: Access denied (HRESULT:-2147024891)' + } + } + + It 'Should throw a terminating error' { + { $mockCimInstance | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Confirm:$false } | Should -Throw -ErrorId 'ASRUR0001,Add-SqlDscRSUrlReservation' + } + } + + Context 'When using WhatIf' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should not call Invoke-RsCimMethod' { + $mockCimInstance | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -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 URL reservation' { + { Add-SqlDscRSUrlReservation -Configuration $mockCimInstance -Application 'ReportServerWebService' -UrlString 'http://+:80' -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-SqlDscRSUrlReservation -Application 'ReportServerWebApp' -UrlString 'http://+:80' -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-SqlDscRSUrlReservation -Application 'ReportManager' -UrlString 'http://+:80' -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-SqlDscRSUrlReservation.Tests.ps1 b/tests/Unit/Public/Get-SqlDscRSUrlReservation.Tests.ps1 new file mode 100644 index 0000000000..25b51d9565 --- /dev/null +++ b/tests/Unit/Public/Get-SqlDscRSUrlReservation.Tests.ps1 @@ -0,0 +1,140 @@ +[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-SqlDscRSUrlReservation' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + } + + Context 'When validating parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-Configuration] []' + } + ) { + $result = (Get-Command -Name 'Get-SqlDscRSUrlReservation').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 URL reservations successfully' { + BeforeAll { + Mock -CommandName Invoke-RsCimMethod -MockWith { + return [PSCustomObject] @{ + HRESULT = 0 + Application = @('ReportServerWebService', 'ReportServerWebApp') + UrlString = @('http://+:80', 'http://+:80') + } + } + } + + It 'Should return URL reservations without errors' { + $result = $mockCimInstance | Get-SqlDscRSUrlReservation + + $result | Should -Not -BeNullOrEmpty + $result.Application | Should -HaveCount 2 + $result.UrlString | Should -HaveCount 2 + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $MethodName -eq 'ListReservedUrls' + } -Exactly -Times 1 + } + } + + Context 'When passing configuration as parameter' { + BeforeAll { + Mock -CommandName Invoke-RsCimMethod -MockWith { + return [PSCustomObject] @{ + HRESULT = 0 + Application = @('ReportServerWebService') + UrlString = @('http://+:80') + } + } + } + + It 'Should get URL reservations' { + $result = Get-SqlDscRSUrlReservation -Configuration $mockCimInstance + + $result | Should -Not -BeNullOrEmpty + $result.Application | Should -Contain 'ReportServerWebService' + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 1 + } + } + + Context 'When CIM method fails with ExtendedErrors' { + BeforeAll { + Mock -CommandName Invoke-RsCimMethod -MockWith { + throw 'Method ListReservedUrls() failed with an error. Error: Access denied;Permission error (HRESULT:-2147024891)' + } + } + + It 'Should throw a terminating error' { + { $mockCimInstance | Get-SqlDscRSUrlReservation } | Should -Throw -ErrorId 'GSRUR0001,Get-SqlDscRSUrlReservation' + } + } + + Context 'When CIM method fails with Error property' { + BeforeAll { + Mock -CommandName Invoke-RsCimMethod -MockWith { + throw 'Method ListReservedUrls() failed with an error. Error: Access denied (HRESULT:-2147024891)' + } + } + + It 'Should throw a terminating error' { + { $mockCimInstance | Get-SqlDscRSUrlReservation } | Should -Throw -ErrorId 'GSRUR0001,Get-SqlDscRSUrlReservation' + } + } +} diff --git a/tests/Unit/Public/Remove-SqlDscRSUrlReservation.Tests.ps1 b/tests/Unit/Public/Remove-SqlDscRSUrlReservation.Tests.ps1 new file mode 100644 index 0000000000..b34850ba49 --- /dev/null +++ b/tests/Unit/Public/Remove-SqlDscRSUrlReservation.Tests.ps1 @@ -0,0 +1,247 @@ +[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-SqlDscRSUrlReservation' { + 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] [-UrlString] [[-Lcid] ] [-PassThru] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Remove-SqlDscRSUrlReservation').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 URL reservation successfully' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should remove URL reservation without errors' { + { $mockCimInstance | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $MethodName -eq 'RemoveURL' -and + $Arguments.Application -eq 'ReportServerWebService' -and + $Arguments.UrlString -eq 'http://+:80' -and + $Arguments.Lcid -eq 1033 + } -Exactly -Times 1 + } + + It 'Should not return anything by default' { + $result = $mockCimInstance | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Confirm:$false + + $result | Should -BeNullOrEmpty + } + } + + Context 'When removing URL reservation with PassThru' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should return the configuration CIM instance' { + $result = $mockCimInstance | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -PassThru -Confirm:$false + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When removing URL reservation with Force' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should remove URL reservation without confirmation' { + { $mockCimInstance | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Force } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 1 + } + } + + Context 'When removing URL reservation with custom Lcid' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should use the specified Lcid' { + { $mockCimInstance | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Lcid 1031 -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $Arguments.Lcid -eq 1031 + } -Exactly -Times 1 + } + + It 'Should not call Get-OperatingSystem when Lcid is specified' { + $mockCimInstance | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Lcid 1031 -Confirm:$false + + Should -Invoke -CommandName Get-OperatingSystem -Exactly -Times 0 + } + } + + Context 'When CIM method fails with ExtendedErrors' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + throw 'Method RemoveURL() failed with an error. Error: Access denied;Permission error (HRESULT:-2147024891)' + } + } + + It 'Should throw a terminating error' { + { $mockCimInstance | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Confirm:$false } | Should -Throw -ErrorId 'RSRUR0001,Remove-SqlDscRSUrlReservation' + } + } + + Context 'When CIM method fails with Error property' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + throw 'Method RemoveURL() failed with an error. Error: Access denied (HRESULT:-2147024891)' + } + } + + It 'Should throw a terminating error' { + { $mockCimInstance | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Confirm:$false } | Should -Throw -ErrorId 'RSRUR0001,Remove-SqlDscRSUrlReservation' + } + } + + Context 'When using WhatIf' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should not call Invoke-RsCimMethod' { + $mockCimInstance | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -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 URL reservation' { + { Remove-SqlDscRSUrlReservation -Configuration $mockCimInstance -Application 'ReportServerWebService' -UrlString 'http://+:80' -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 | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebApp' -UrlString 'http://+:80' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $Arguments.Application -eq 'ReportServerWebApp' + } -Exactly -Times 1 + } + + It 'Should accept ReportManager application' { + { $mockCimInstance | Remove-SqlDscRSUrlReservation -Application 'ReportManager' -UrlString 'http://+:80' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $Arguments.Application -eq 'ReportManager' + } -Exactly -Times 1 + } + } +} From 51d5d8cb0167da4ba2ac9e631eb04de37f31bcd6 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 11:15:38 +0100 Subject: [PATCH 02/14] Refactor URL reservation commands for SQL Server Reporting Services to improve clarity and functionality --- CHANGELOG.md | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36a6d3ae6a..24d0e64f98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,26 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Added public command `Get-SqlDscRSUrlReservation` to retrieve URL reservations - for SQL Server Reporting Services or Power BI Report Server by calling the - `ListReservedUrls` CIM method. Returns the raw CIM method result containing - Application and UrlString arrays - ([issue #2016](https://github.com/dsccommunity/SqlServerDsc/issues/2016)). -- Added public command `Add-SqlDscRSUrlReservation` to add URL reservations for - SQL Server Reporting Services or Power BI Report Server by calling the - `ReserveUrl` CIM method. Supports ReportServerWebService, ReportServerWebApp, - and ReportManager application types. Accepts pipeline input, supports - `-WhatIf`/`-Confirm`, `-PassThru`, and `-Force` - ([issue #2016](https://github.com/dsccommunity/SqlServerDsc/issues/2016)). -- Added public command `Remove-SqlDscRSUrlReservation` to remove URL reservations - for SQL Server Reporting Services or Power BI Report Server by calling the - `RemoveURL` CIM method. Supports ReportServerWebService, ReportServerWebApp, - and ReportManager application types. Accepts pipeline input, supports - `-WhatIf`/`-Confirm`, `-PassThru`, and `-Force` +- Added public commands `Get-SqlDscRSUrlReservation`, `Add-SqlDscRSUrlReservation`, + and `Remove-SqlDscRSUrlReservation` to manage URL reservations for SQL Server + Reporting Services or Power BI Report Server. These commands wrap the + `ListReservedUrls`, `ReserveUrl`, and `RemoveURL` CIM methods respectively + ([issue #2016](https://github.com/dsccommunity/SqlServerDsc/issues/2016)) ([issue #2024](https://github.com/dsccommunity/SqlServerDsc/issues/2024)). -- Added private function `Get-OperatingSystem` to retrieve operating system - information via the Win32_OperatingSystem CIM class. Used to get the OS - language code (OSLanguage) for URL reservation operations. - Added public command `Get-SqlDscRSConfiguration` to retrieve the `MSReportServer_ConfigurationSetting` CIM instance for SQL Server Reporting Services or Power BI Report Server. Supports auto-detection of the Reporting From b1a3cb0bbad73f744ef5428c953f02444680f9cf Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 11:21:01 +0100 Subject: [PATCH 03/14] Refactor output type declaration and enhance error handling in URL reservation functions and tests --- source/Public/Add-SqlDscRSUrlReservation.ps1 | 1 - .../Public/Remove-SqlDscRSUrlReservation.ps1 | 1 - ...lDscRSUrlReservation.Integration.Tests.ps1 | 48 +++++++++---------- ...lDscRSUrlReservation.Integration.Tests.ps1 | 16 +++++-- .../Private/Get-OperatingSystem.Tests.ps1 | 2 +- 5 files changed, 37 insertions(+), 31 deletions(-) diff --git a/source/Public/Add-SqlDscRSUrlReservation.ps1 b/source/Public/Add-SqlDscRSUrlReservation.ps1 index 9bb2b940ff..e26fd071ca 100644 --- a/source/Public/Add-SqlDscRSUrlReservation.ps1 +++ b/source/Public/Add-SqlDscRSUrlReservation.ps1 @@ -89,7 +89,6 @@ function Add-SqlDscRSUrlReservation # cSpell: ignore PBIRS [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] - [OutputType()] [OutputType([System.Object])] param ( diff --git a/source/Public/Remove-SqlDscRSUrlReservation.ps1 b/source/Public/Remove-SqlDscRSUrlReservation.ps1 index 0c4f47f5fb..7188005a5e 100644 --- a/source/Public/Remove-SqlDscRSUrlReservation.ps1 +++ b/source/Public/Remove-SqlDscRSUrlReservation.ps1 @@ -89,7 +89,6 @@ function Remove-SqlDscRSUrlReservation # cSpell: ignore PBIRS [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] - [OutputType()] [OutputType([System.Object])] param ( diff --git a/tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1 b/tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1 index e091403e18..0584e78dfe 100644 --- a/tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1 @@ -34,7 +34,7 @@ BeforeAll { Describe 'Add-SqlDscRSUrlReservation' { Context 'When adding URL reservation for SQL Server Reporting Services' -Tag @('Integration_SQL2017_RS') { BeforeAll { - $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' # Store original URL reservations to restore later $script:originalReservations = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' @@ -58,20 +58,20 @@ Describe 'Add-SqlDscRSUrlReservation' { } It 'Should add URL reservation using pipeline' { - { $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' } | Should -Not -Throw + $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' # Verify the URL was added - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' - $reservations.UrlReservations | Should -Contain $script:testUrl + $reservations.UrlString | Should -Contain $script:testUrl } It 'Should return configuration when using PassThru' { # First remove the test URL if it exists try { - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' } catch @@ -79,7 +79,7 @@ Describe 'Add-SqlDscRSUrlReservation' { # Ignore } - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' $result = $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -PassThru -ErrorAction 'Stop' $result | Should -Not -BeNullOrEmpty @@ -89,7 +89,7 @@ Describe 'Add-SqlDscRSUrlReservation' { Context 'When adding URL reservation for SQL Server Reporting Services' -Tag @('Integration_SQL2019_RS') { BeforeAll { - $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' # Use a unique port for testing to avoid conflicts $script:testPort = 18080 @@ -110,20 +110,20 @@ Describe 'Add-SqlDscRSUrlReservation' { } It 'Should add URL reservation using pipeline' { - { $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' } | Should -Not -Throw + $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' # Verify the URL was added - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' - $reservations.UrlReservations | Should -Contain $script:testUrl + $reservations.UrlString | Should -Contain $script:testUrl } It 'Should return configuration when using PassThru' { # First remove the test URL if it exists try { - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' } catch @@ -131,7 +131,7 @@ Describe 'Add-SqlDscRSUrlReservation' { # Ignore } - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' $result = $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -PassThru -ErrorAction 'Stop' $result | Should -Not -BeNullOrEmpty @@ -141,7 +141,7 @@ Describe 'Add-SqlDscRSUrlReservation' { Context 'When adding URL reservation for SQL Server Reporting Services' -Tag @('Integration_SQL2022_RS') { BeforeAll { - $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' # Use a unique port for testing to avoid conflicts $script:testPort = 18080 @@ -162,20 +162,20 @@ Describe 'Add-SqlDscRSUrlReservation' { } It 'Should add URL reservation using pipeline' { - { $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' } | Should -Not -Throw + $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' # Verify the URL was added - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' - $reservations.UrlReservations | Should -Contain $script:testUrl + $reservations.UrlString | Should -Contain $script:testUrl } It 'Should return configuration when using PassThru' { # First remove the test URL if it exists try { - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' } catch @@ -183,7 +183,7 @@ Describe 'Add-SqlDscRSUrlReservation' { # Ignore } - $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' $result = $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -PassThru -ErrorAction 'Stop' $result | Should -Not -BeNullOrEmpty @@ -194,7 +194,7 @@ Describe 'Add-SqlDscRSUrlReservation' { Context 'When adding URL reservation for Power BI Report Server' -Tag @('Integration_PowerBI') { # cSpell: ignore PBIRS BeforeAll { - $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' # Use a unique port for testing to avoid conflicts $script:testPort = 18080 @@ -215,20 +215,20 @@ Describe 'Add-SqlDscRSUrlReservation' { } It 'Should add URL reservation for PBIRS using pipeline' { - { $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' } | Should -Not -Throw + $script:configuration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'Stop' # Verify the URL was added - $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' $reservations = $config | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' - $reservations.UrlReservations | Should -Contain $script:testUrl + $reservations.UrlString | Should -Contain $script:testUrl } It 'Should return configuration when using PassThru' { # First remove the test URL if it exists try { - $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -ErrorAction 'SilentlyContinue' } catch @@ -236,7 +236,7 @@ Describe 'Add-SqlDscRSUrlReservation' { # Ignore } - $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' $result = $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl -Force -PassThru -ErrorAction 'Stop' $result | Should -Not -BeNullOrEmpty diff --git a/tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1 b/tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1 index 379da0035e..6c9b2b6065 100644 --- a/tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1 @@ -34,7 +34,7 @@ BeforeAll { Describe 'Get-SqlDscRSUrlReservation' { Context 'When getting URL reservations for SQL Server Reporting Services' -Tag @('Integration_SQL2017_RS') { BeforeAll { - $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' } It 'Should return URL reservations using pipeline' { @@ -55,12 +55,14 @@ Describe 'Get-SqlDscRSUrlReservation' { # The result should be a CIM method result with URL reservation properties $result | Should -Not -BeNullOrEmpty $result.HRESULT | Should -Be 0 + $result.Application | Should -Not -BeNullOrEmpty + $result.UrlString | Should -Not -BeNullOrEmpty } } Context 'When getting URL reservations for SQL Server Reporting Services' -Tag @('Integration_SQL2019_RS') { BeforeAll { - $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' } It 'Should return URL reservations using pipeline' { @@ -81,12 +83,14 @@ Describe 'Get-SqlDscRSUrlReservation' { # The result should be a CIM method result with URL reservation properties $result | Should -Not -BeNullOrEmpty $result.HRESULT | Should -Be 0 + $result.Application | Should -Not -BeNullOrEmpty + $result.UrlString | Should -Not -BeNullOrEmpty } } Context 'When getting URL reservations for SQL Server Reporting Services' -Tag @('Integration_SQL2022_RS') { BeforeAll { - $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' } It 'Should return URL reservations using pipeline' { @@ -107,13 +111,15 @@ Describe 'Get-SqlDscRSUrlReservation' { # The result should be a CIM method result with URL reservation properties $result | Should -Not -BeNullOrEmpty $result.HRESULT | Should -Be 0 + $result.Application | Should -Not -BeNullOrEmpty + $result.UrlString | Should -Not -BeNullOrEmpty } } Context 'When getting URL reservations for Power BI Report Server' -Tag @('Integration_PowerBI') { # cSpell: ignore PBIRS BeforeAll { - $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' } It 'Should return URL reservations using pipeline' { @@ -134,6 +140,8 @@ Describe 'Get-SqlDscRSUrlReservation' { # The result should be a CIM method result with URL reservation properties $result | Should -Not -BeNullOrEmpty $result.HRESULT | Should -Be 0 + $result.Application | Should -Not -BeNullOrEmpty + $result.UrlString | Should -Not -BeNullOrEmpty } } } diff --git a/tests/Unit/Private/Get-OperatingSystem.Tests.ps1 b/tests/Unit/Private/Get-OperatingSystem.Tests.ps1 index f04b6d608e..99bb772417 100644 --- a/tests/Unit/Private/Get-OperatingSystem.Tests.ps1 +++ b/tests/Unit/Private/Get-OperatingSystem.Tests.ps1 @@ -66,7 +66,7 @@ BeforeAll { } AfterAll { - $env:SqlServerDscCI = $null + Remove-Item -Path 'env:SqlServerDscCI' -Force -ErrorAction 'SilentlyContinue' InModuleScope -ScriptBlock { Remove-Item -Path 'function:script:Get-CimInstance' -Force -ErrorAction SilentlyContinue From eb749ff99fa3f679d960fd024927df49ca8c2d76 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 12:08:49 +0100 Subject: [PATCH 04/14] Add Set-SqlDscRSUrlReservation function to manage URL reservations for Reporting Services --- CHANGELOG.md | 9 +- azure-pipelines.yml | 2 + source/Public/Set-SqlDscRSUrlReservation.ps1 | 194 ++++++++ source/en-US/SqlServerDsc.strings.psd1 | 10 + tests/Integration/Commands/README.md | 2 + ...lDscRSUrlReservation.Integration.Tests.ps1 | 434 ++++++++++++++++++ .../Set-SqlDscRSUrlReservation.Tests.ps1 | 331 +++++++++++++ 7 files changed, 979 insertions(+), 3 deletions(-) create mode 100644 source/Public/Set-SqlDscRSUrlReservation.ps1 create mode 100644 tests/Integration/Commands/Set-SqlDscRSUrlReservation.Integration.Tests.ps1 create mode 100644 tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 24d0e64f98..dd4d7de324 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added public commands `Get-SqlDscRSUrlReservation`, `Add-SqlDscRSUrlReservation`, - and `Remove-SqlDscRSUrlReservation` to manage URL reservations for SQL Server - Reporting Services or Power BI Report Server. These commands wrap the - `ListReservedUrls`, `ReserveUrl`, and `RemoveURL` CIM methods respectively + `Remove-SqlDscRSUrlReservation`, and `Set-SqlDscRSUrlReservation` to manage + URL reservations for SQL Server Reporting Services or Power BI Report Server. + These commands wrap the `ListReservedUrls`, `ReserveUrl`, and `RemoveURL` CIM + methods respectively. The `Set-SqlDscRSUrlReservation` command provides a + declarative approach to set URL reservations to an exact list, removing any + existing reservations not in the specified list ([issue #2016](https://github.com/dsccommunity/SqlServerDsc/issues/2016)) ([issue #2024](https://github.com/dsccommunity/SqlServerDsc/issues/2024)). - Added public command `Get-SqlDscRSConfiguration` to retrieve the diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 711a5b52e1..0cba919663 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -542,6 +542,7 @@ stages: 'tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1' 'tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1' 'tests/Integration/Commands/Remove-SqlDscRSUrlReservation.Integration.Tests.ps1' + 'tests/Integration/Commands/Set-SqlDscRSUrlReservation.Integration.Tests.ps1' # Group 8 'tests/Integration/Commands/Repair-SqlDscReportingService.Integration.Tests.ps1' # Group 9 @@ -615,6 +616,7 @@ stages: 'tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1' 'tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1' 'tests/Integration/Commands/Remove-SqlDscRSUrlReservation.Integration.Tests.ps1' + 'tests/Integration/Commands/Set-SqlDscRSUrlReservation.Integration.Tests.ps1' # Group 8 'tests/Integration/Commands/Repair-SqlDscPowerBIReportServer.Integration.Tests.ps1' # Group 9 diff --git a/source/Public/Set-SqlDscRSUrlReservation.ps1 b/source/Public/Set-SqlDscRSUrlReservation.ps1 new file mode 100644 index 0000000000..c5c803a86d --- /dev/null +++ b/source/Public/Set-SqlDscRSUrlReservation.ps1 @@ -0,0 +1,194 @@ +<# + .SYNOPSIS + Sets the URL reservations for a SQL Server Reporting Services or Power BI + Report Server application to the specified list. + + .DESCRIPTION + The `Set-SqlDscRSUrlReservation` command ensures that only the specified + URL reservations exist for the given application. It removes any existing + URL reservations that are not in the specified list and adds any URLs that + are not currently reserved. + + This command uses the `Get-SqlDscRSUrlReservation`, `Add-SqlDscRSUrlReservation`, + and `Remove-SqlDscRSUrlReservation` commands internally. + + .PARAMETER Configuration + Specifies the Reporting Services configuration CIM instance. This is typically + obtained by calling the `Get-SqlDscRSConfiguration` command. + + .PARAMETER Application + Specifies the Reporting Services application for which to set URL reservations. + Valid values are: ReportServerWebService, ReportServerWebApp, ReportManager. + + .PARAMETER UrlString + Specifies one or more URL strings to reserve. Any existing URL reservations + for the application that are not in this list will be removed. + + .PARAMETER Lcid + Specifies the locale identifier (LCID) for the URL reservation. If not + specified, the operating system language code is used. + + .PARAMETER PassThru + If specified, returns the Reporting Services configuration CIM instance. + + .PARAMETER Force + If specified, suppresses the confirmation prompt. + + .INPUTS + Microsoft.Management.Infrastructure.CimInstance + + Accepts a CIM instance for the Reporting Services configuration from the + pipeline. + + .OUTPUTS + None by default. + + Microsoft.Management.Infrastructure.CimInstance + + If `-PassThru` is specified, returns the Reporting Services configuration + CIM instance. + + .EXAMPLE + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + $config | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80', 'https://+:443' -Force + + Sets the URL reservations for the ReportServerWebService application to + only 'http://+:80' and 'https://+:443'. Any other existing reservations + for this application will be removed. + + .EXAMPLE + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' + $config | Set-SqlDscRSUrlReservation -Application 'ReportServerWebApp' -UrlString 'http://+:80' -Force -PassThru + + Sets the URL reservations for the ReportServerWebApp application on a + Power BI Report Server instance and returns the configuration object. + + .NOTES + This command calls the ReserveUrl and RemoveURL methods on the + MSReportServer_ConfigurationSetting CIM class. + + .LINK + Get-SqlDscRSUrlReservation + + .LINK + Add-SqlDscRSUrlReservation + + .LINK + Remove-SqlDscRSUrlReservation + + .LINK + https://docs.microsoft.com/en-us/sql/reporting-services/wmi-provider-library-reference/configurationsetting-method-reserveurl + + .LINK + https://docs.microsoft.com/en-us/sql/reporting-services/wmi-provider-library-reference/configurationsetting-method-removeurl +#> +function Set-SqlDscRSUrlReservation +{ + # cSpell: ignore PBIRS + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] + [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[]] + $UrlString, + + [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 + + $verboseDescriptionMessage = $script:localizedData.Set_SqlDscRSUrlReservation_ShouldProcessVerboseDescription -f $Application, $instanceName + $verboseWarningMessage = $script:localizedData.Set_SqlDscRSUrlReservation_ShouldProcessVerboseWarning -f $Application, $instanceName + $captionMessage = $script:localizedData.Set_SqlDscRSUrlReservation_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($verboseDescriptionMessage, $verboseWarningMessage, $captionMessage)) + { + # Get current URL reservations + $currentReservations = $Configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + # Build a list of current URLs for the specified application + $currentUrls = @() + + if ($null -ne $currentReservations.Application -and $null -ne $currentReservations.UrlString) + { + for ($i = 0; $i -lt $currentReservations.Application.Count; $i++) + { + if ($currentReservations.Application[$i] -eq $Application) + { + $currentUrls += $currentReservations.UrlString[$i] + } + } + } + + Write-Verbose -Message ($script:localizedData.Set_SqlDscRSUrlReservation_CurrentUrls -f $Application, ($currentUrls -join ', ')) + Write-Verbose -Message ($script:localizedData.Set_SqlDscRSUrlReservation_DesiredUrls -f $Application, ($UrlString -join ', ')) + + # Determine URLs to remove (in current but not in desired) + $urlsToRemove = $currentUrls | Where-Object -FilterScript { $_ -notin $UrlString } + + # Determine URLs to add (in desired but not in current) + $urlsToAdd = $UrlString | Where-Object -FilterScript { $_ -notin $currentUrls } + + # Build common parameters for Add/Remove commands + $commonParams = @{ + Application = $Application + Force = $true + ErrorAction = 'Stop' + } + + if ($PSBoundParameters.ContainsKey('Lcid')) + { + $commonParams['Lcid'] = $Lcid + } + + # Remove URLs that should not exist + foreach ($urlToRemove in $urlsToRemove) + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscRSUrlReservation_RemovingUrl -f $urlToRemove, $Application, $instanceName) + + $Configuration | Remove-SqlDscRSUrlReservation @commonParams -UrlString $urlToRemove + } + + # Add URLs that should exist + foreach ($urlToAdd in $urlsToAdd) + { + Write-Verbose -Message ($script:localizedData.Set_SqlDscRSUrlReservation_AddingUrl -f $urlToAdd, $Application, $instanceName) + + $Configuration | Add-SqlDscRSUrlReservation @commonParams -UrlString $urlToAdd + } + + if ($PassThru.IsPresent) + { + return $Configuration + } + } + } +} diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 5e72fe4c05..01c42f38be 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -730,4 +730,14 @@ ConvertFrom-StringData @' # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. Remove_SqlDscRSUrlReservation_ShouldProcessCaption = Remove URL reservation for Reporting Services instance Remove_SqlDscRSUrlReservation_FailedToRemove = Failed to remove URL reservation for Reporting Services instance '{0}'. {1} (RSRUR0001) + + ## Set-SqlDscRSUrlReservation + Set_SqlDscRSUrlReservation_ShouldProcessVerboseDescription = Setting URL reservations for application '{0}' on Reporting Services instance '{1}'. + Set_SqlDscRSUrlReservation_ShouldProcessVerboseWarning = Are you sure you want to set URL reservations for application '{0}' on Reporting Services instance '{1}'? Existing reservations not in the specified list will be removed. + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Set_SqlDscRSUrlReservation_ShouldProcessCaption = Set URL reservations for Reporting Services instance + Set_SqlDscRSUrlReservation_CurrentUrls = Current URL reservations for application '{0}': {1} + Set_SqlDscRSUrlReservation_DesiredUrls = Desired URL reservations for application '{0}': {1} + Set_SqlDscRSUrlReservation_RemovingUrl = Removing URL reservation '{0}' for application '{1}' on Reporting Services instance '{2}'. + Set_SqlDscRSUrlReservation_AddingUrl = Adding URL reservation '{0}' for application '{1}' on Reporting Services instance '{2}'. '@ diff --git a/tests/Integration/Commands/README.md b/tests/Integration/Commands/README.md index 20843c14f1..3016c2825a 100644 --- a/tests/Integration/Commands/README.md +++ b/tests/Integration/Commands/README.md @@ -171,6 +171,7 @@ Disable-SqlDscRsSecureConnection | 3 | 1 (Install-SqlDscReportingService), 0 (Pr Get-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - Add-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - Remove-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - +Set-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscReportingService), 0 (Prerequisites) | SSRS | - Repair-SqlDscReportingService | 8 | 1 (Install-SqlDscReportingService) | SSRS | - Uninstall-SqlDscReportingService | 9 | 8 (Repair-SqlDscReportingService) | - | - @@ -196,6 +197,7 @@ Disable-SqlDscRsSecureConnection | 3 | 1 (Install-SqlDscPowerBIReportServer), 0 Get-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - Add-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - Remove-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - +Set-SqlDscRSUrlReservation | 3 | 1 (Install-SqlDscPowerBIReportServer), 0 (Prerequisites) | PBIRS | - Repair-SqlDscPowerBIReportServer | 8 | 1 (Install-SqlDscPowerBIReportServer) | PBIRS | - Uninstall-SqlDscPowerBIReportServer | 9 | 8 (Repair-SqlDscPowerBIReportServer) | - | - diff --git a/tests/Integration/Commands/Set-SqlDscRSUrlReservation.Integration.Tests.ps1 b/tests/Integration/Commands/Set-SqlDscRSUrlReservation.Integration.Tests.ps1 new file mode 100644 index 0000000000..ee57dd98d6 --- /dev/null +++ b/tests/Integration/Commands/Set-SqlDscRSUrlReservation.Integration.Tests.ps1 @@ -0,0 +1,434 @@ +[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 'Set-SqlDscRSUrlReservation' { + Context 'When setting URL reservations for SQL Server Reporting Services' -Tag @('Integration_SQL2017_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + + # Store original URL reservations to restore later + $script:originalReservations = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + # Get current URLs for ReportServerWebService to restore later + $script:originalWebServiceUrls = @() + + if ($null -ne $script:originalReservations.Application) + { + for ($i = 0; $i -lt $script:originalReservations.Application.Count; $i++) + { + if ($script:originalReservations.Application[$i] -eq 'ReportServerWebService') + { + $script:originalWebServiceUrls += $script:originalReservations.UrlString[$i] + } + } + } + + # Use unique ports for testing to avoid conflicts + $script:testUrl1 = 'http://+:18081' + $script:testUrl2 = 'http://+:18082' + } + + AfterAll { + # Clean up: restore original URL reservations + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + + # Remove test URLs if they exist + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1 -Force -ErrorAction 'SilentlyContinue' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl2 -Force -ErrorAction 'SilentlyContinue' + + # Restore original URLs + foreach ($url in $script:originalWebServiceUrls) + { + $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $url -Force -ErrorAction 'SilentlyContinue' + } + } + catch + { + # Ignore errors during cleanup + } + } + + It 'Should set URL reservations using pipeline' { + $script:configuration | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1 -Force -ErrorAction 'Stop' + + # Verify the URLs are set correctly + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -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 add multiple URLs and remove existing ones' { + $script:configuration | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1, $script:testUrl2 -Force -ErrorAction 'Stop' + + # Verify both URLs are set + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -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 + $currentUrls | Should -Contain $script:testUrl2 + } + + It 'Should return configuration when using PassThru' { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + $result = $config | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1 -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When setting URL reservations for SQL Server Reporting Services' -Tag @('Integration_SQL2019_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + + # Store original URL reservations to restore later + $script:originalReservations = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + # Get current URLs for ReportServerWebService to restore later + $script:originalWebServiceUrls = @() + + if ($null -ne $script:originalReservations.Application) + { + for ($i = 0; $i -lt $script:originalReservations.Application.Count; $i++) + { + if ($script:originalReservations.Application[$i] -eq 'ReportServerWebService') + { + $script:originalWebServiceUrls += $script:originalReservations.UrlString[$i] + } + } + } + + # Use unique ports for testing to avoid conflicts + $script:testUrl1 = 'http://+:18081' + $script:testUrl2 = 'http://+:18082' + } + + AfterAll { + # Clean up: restore original URL reservations + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + + # Remove test URLs if they exist + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1 -Force -ErrorAction 'SilentlyContinue' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl2 -Force -ErrorAction 'SilentlyContinue' + + # Restore original URLs + foreach ($url in $script:originalWebServiceUrls) + { + $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $url -Force -ErrorAction 'SilentlyContinue' + } + } + catch + { + # Ignore errors during cleanup + } + } + + It 'Should set URL reservations using pipeline' { + $script:configuration | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1 -Force -ErrorAction 'Stop' + + # Verify the URLs are set correctly + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -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 add multiple URLs and remove existing ones' { + $script:configuration | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1, $script:testUrl2 -Force -ErrorAction 'Stop' + + # Verify both URLs are set + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -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 + $currentUrls | Should -Contain $script:testUrl2 + } + + It 'Should return configuration when using PassThru' { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + $result = $config | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1 -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When setting URL reservations for SQL Server Reporting Services' -Tag @('Integration_SQL2022_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + + # Store original URL reservations to restore later + $script:originalReservations = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + # Get current URLs for ReportServerWebService to restore later + $script:originalWebServiceUrls = @() + + if ($null -ne $script:originalReservations.Application) + { + for ($i = 0; $i -lt $script:originalReservations.Application.Count; $i++) + { + if ($script:originalReservations.Application[$i] -eq 'ReportServerWebService') + { + $script:originalWebServiceUrls += $script:originalReservations.UrlString[$i] + } + } + } + + # Use unique ports for testing to avoid conflicts + $script:testUrl1 = 'http://+:18081' + $script:testUrl2 = 'http://+:18082' + } + + AfterAll { + # Clean up: restore original URL reservations + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + + # Remove test URLs if they exist + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1 -Force -ErrorAction 'SilentlyContinue' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl2 -Force -ErrorAction 'SilentlyContinue' + + # Restore original URLs + foreach ($url in $script:originalWebServiceUrls) + { + $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $url -Force -ErrorAction 'SilentlyContinue' + } + } + catch + { + # Ignore errors during cleanup + } + } + + It 'Should set URL reservations using pipeline' { + $script:configuration | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1 -Force -ErrorAction 'Stop' + + # Verify the URLs are set correctly + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -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 add multiple URLs and remove existing ones' { + $script:configuration | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1, $script:testUrl2 -Force -ErrorAction 'Stop' + + # Verify both URLs are set + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -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 + $currentUrls | Should -Contain $script:testUrl2 + } + + It 'Should return configuration when using PassThru' { + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + $result = $config | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1 -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When setting URL reservations for Power BI Report Server' -Tag @('Integration_PBIRS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + + # Store original URL reservations to restore later + $script:originalReservations = $script:configuration | Get-SqlDscRSUrlReservation -ErrorAction 'Stop' + + # Get current URLs for ReportServerWebService to restore later + $script:originalWebServiceUrls = @() + + if ($null -ne $script:originalReservations.Application) + { + for ($i = 0; $i -lt $script:originalReservations.Application.Count; $i++) + { + if ($script:originalReservations.Application[$i] -eq 'ReportServerWebService') + { + $script:originalWebServiceUrls += $script:originalReservations.UrlString[$i] + } + } + } + + # Use unique ports for testing to avoid conflicts + $script:testUrl1 = 'http://+:18081' + $script:testUrl2 = 'http://+:18082' + } + + AfterAll { + # Clean up: restore original URL reservations + try + { + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + + # Remove test URLs if they exist + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1 -Force -ErrorAction 'SilentlyContinue' + $config | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl2 -Force -ErrorAction 'SilentlyContinue' + + # Restore original URLs + foreach ($url in $script:originalWebServiceUrls) + { + $config | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $url -Force -ErrorAction 'SilentlyContinue' + } + } + catch + { + # Ignore errors during cleanup + } + } + + It 'Should set URL reservations using pipeline' { + $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 add multiple URLs and remove existing ones' { + $script:configuration | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1, $script:testUrl2 -Force -ErrorAction 'Stop' + + # Verify both URLs are set + $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 + $currentUrls | Should -Contain $script:testUrl2 + } + + It 'Should return configuration when using PassThru' { + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + $result = $config | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $script:testUrl1 -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'PBIRS' + } + } +} diff --git a/tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 b/tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 new file mode 100644 index 0000000000..ed25980aa4 --- /dev/null +++ b/tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 @@ -0,0 +1,331 @@ +[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 + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:moduleName +} + +AfterAll { + Remove-Item -Path 'env:SqlServerDscCI' -Force -ErrorAction 'SilentlyContinue' + + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') +} + +Describe 'Set-SqlDscRSUrlReservation' -Tag 'Public' { + Context 'When validating parameter sets' { + BeforeAll { + $command = Get-Command -Name 'Set-SqlDscRSUrlReservation' + } + + It 'Should have the correct parameters in parameter set __AllParameterSets' { + $ExpectedParameters = '[-Configuration] [-Application] [-UrlString] [[-Lcid] ] [-PassThru] [-Force] [-WhatIf] [-Confirm] []' + + $result = $command.ParameterSets | + Where-Object -FilterScript { $_.Name -eq '__AllParameterSets' } | + Select-Object -Property @{ + Name = 'ParameterListAsString' + Expression = { $_.ToString() } + } + + $result.ParameterListAsString | Should -Be $ExpectedParameters + } + } + + Context 'When setting URL reservations with no changes needed' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSUrlReservation -MockWith { + return [PSCustomObject] @{ + HRESULT = 0 + Application = @('ReportServerWebService', 'ReportServerWebService') + UrlString = @('http://+:80', 'https://+:443') + } + } + + Mock -CommandName Add-SqlDscRSUrlReservation + Mock -CommandName Remove-SqlDscRSUrlReservation + } + + It 'Should not add or remove any URLs when current matches desired' { + $mockCimInstance | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80', 'https://+:443' -Force + + Should -Invoke -CommandName Get-SqlDscRSUrlReservation -Exactly -Times 1 + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -Exactly -Times 0 + Should -Invoke -CommandName Remove-SqlDscRSUrlReservation -Exactly -Times 0 + } + } + + Context 'When adding new URL reservations' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSUrlReservation -MockWith { + return [PSCustomObject] @{ + HRESULT = 0 + Application = @('ReportServerWebService') + UrlString = @('http://+:80') + } + } + + Mock -CommandName Add-SqlDscRSUrlReservation + Mock -CommandName Remove-SqlDscRSUrlReservation + } + + It 'Should add the new URL' { + $mockCimInstance | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80', 'https://+:443' -Force + + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $UrlString -eq 'https://+:443' + } -Exactly -Times 1 + + Should -Invoke -CommandName Remove-SqlDscRSUrlReservation -Exactly -Times 0 + } + } + + Context 'When removing existing URL reservations' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSUrlReservation -MockWith { + return [PSCustomObject] @{ + HRESULT = 0 + Application = @('ReportServerWebService', 'ReportServerWebService') + UrlString = @('http://+:80', 'https://+:443') + } + } + + Mock -CommandName Add-SqlDscRSUrlReservation + Mock -CommandName Remove-SqlDscRSUrlReservation + } + + It 'Should remove the URL not in the desired list' { + $mockCimInstance | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Force + + Should -Invoke -CommandName Remove-SqlDscRSUrlReservation -ParameterFilter { + $UrlString -eq 'https://+:443' + } -Exactly -Times 1 + + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -Exactly -Times 0 + } + } + + Context 'When both adding and removing URL reservations' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSUrlReservation -MockWith { + return [PSCustomObject] @{ + HRESULT = 0 + Application = @('ReportServerWebService', 'ReportServerWebService') + UrlString = @('http://+:80', 'http://+:8080') + } + } + + Mock -CommandName Add-SqlDscRSUrlReservation + Mock -CommandName Remove-SqlDscRSUrlReservation + } + + It 'Should add new and remove old URLs' { + $mockCimInstance | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80', 'https://+:443' -Force + + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $UrlString -eq 'https://+:443' + } -Exactly -Times 1 + + Should -Invoke -CommandName Remove-SqlDscRSUrlReservation -ParameterFilter { + $UrlString -eq 'http://+:8080' + } -Exactly -Times 1 + } + } + + Context 'When using PassThru' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSUrlReservation -MockWith { + return [PSCustomObject] @{ + HRESULT = 0 + Application = @() + UrlString = @() + } + } + + Mock -CommandName Add-SqlDscRSUrlReservation + Mock -CommandName Remove-SqlDscRSUrlReservation + } + + It 'Should return the configuration CIM instance' { + $result = $mockCimInstance | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Force -PassThru + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When using custom Lcid' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSUrlReservation -MockWith { + return [PSCustomObject] @{ + HRESULT = 0 + Application = @() + UrlString = @() + } + } + + Mock -CommandName Add-SqlDscRSUrlReservation + Mock -CommandName Remove-SqlDscRSUrlReservation + } + + It 'Should pass Lcid to Add-SqlDscRSUrlReservation' { + $mockCimInstance | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Lcid 1031 -Force + + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $Lcid -eq 1031 + } -Exactly -Times 1 + } + } + + Context 'When using WhatIf' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSUrlReservation + Mock -CommandName Add-SqlDscRSUrlReservation + Mock -CommandName Remove-SqlDscRSUrlReservation + } + + It 'Should not call any modification commands' { + $mockCimInstance | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -WhatIf + + Should -Invoke -CommandName Get-SqlDscRSUrlReservation -Exactly -Times 0 + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -Exactly -Times 0 + Should -Invoke -CommandName Remove-SqlDscRSUrlReservation -Exactly -Times 0 + } + } + + Context 'When passing configuration as parameter' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSUrlReservation -MockWith { + return [PSCustomObject] @{ + HRESULT = 0 + Application = @() + UrlString = @() + } + } + + Mock -CommandName Add-SqlDscRSUrlReservation + Mock -CommandName Remove-SqlDscRSUrlReservation + } + + It 'Should set URL reservations' { + { Set-SqlDscRSUrlReservation -Configuration $mockCimInstance -Application 'ReportServerWebService' -UrlString 'http://+:80' -Force } | Should -Not -Throw + + Should -Invoke -CommandName Get-SqlDscRSUrlReservation -Exactly -Times 1 + } + } + + Context 'When no current reservations exist for application' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSUrlReservation -MockWith { + return [PSCustomObject] @{ + HRESULT = 0 + Application = @('ReportServerWebApp') + UrlString = @('http://+:80') + } + } + + Mock -CommandName Add-SqlDscRSUrlReservation + Mock -CommandName Remove-SqlDscRSUrlReservation + } + + It 'Should add all specified URLs' { + $mockCimInstance | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80', 'https://+:443' -Force + + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -Exactly -Times 2 + Should -Invoke -CommandName Remove-SqlDscRSUrlReservation -Exactly -Times 0 + } + } + + Context 'When current reservations is null' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Get-SqlDscRSUrlReservation -MockWith { + return [PSCustomObject] @{ + HRESULT = 0 + Application = $null + UrlString = $null + } + } + + Mock -CommandName Add-SqlDscRSUrlReservation + Mock -CommandName Remove-SqlDscRSUrlReservation + } + + It 'Should add all specified URLs' { + $mockCimInstance | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString 'http://+:80' -Force + + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -Exactly -Times 1 + Should -Invoke -CommandName Remove-SqlDscRSUrlReservation -Exactly -Times 0 + } + } +} From 845218af1524464e701bad76bfc411d046291975 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 14:11:12 +0100 Subject: [PATCH 05/14] Add SQL Server Reporting Services (SSRS) DSC functions and integration tests - Implemented Get-SqlDscRSVersion to retrieve the SQL Server Reporting Services version. - Created Get-SqlDscRSWebPortalApplicationName to determine the web portal application name based on SQL Server version. - Developed Set-SqlDscRSVirtualDirectory to configure the virtual directory for SSRS and Power BI Report Server. - Added integration tests for Set-SqlDscRSVirtualDirectory covering various SQL Server versions and scenarios. - Created unit tests for Get-SqlDscRSVersion and Get-SqlDscRSWebPortalApplicationName to validate functionality and error handling. - Enhanced error handling and logging across the new functions. --- CHANGELOG.md | 13 + azure-pipelines.yml | 2 + source/DSCResources/DSC_SqlRS/DSC_SqlRS.psm1 | 323 +++---------- source/Private/Get-OperatingSystem.ps1 | 6 +- source/Public/Get-SqlDscRSUrlReservation.ps1 | 4 +- source/Public/Get-SqlDscRSVersion.ps1 | 56 +++ .../Get-SqlDscRSWebPortalApplicationName.ps1 | 79 +++ .../Public/Set-SqlDscRSVirtualDirectory.ps1 | 175 +++++++ source/en-US/SqlServerDsc.strings.psd1 | 16 + ...lDscRSUrlReservation.Integration.Tests.ps1 | 40 +- ...scRSVirtualDirectory.Integration.Tests.ps1 | 123 +++++ tests/Unit/DSC_SqlRS.Tests.ps1 | 450 ++++++------------ .../Unit/Public/Get-SqlDscRSVersion.Tests.ps1 | 165 +++++++ ...SqlDscRSWebPortalApplicationName.Tests.ps1 | 195 ++++++++ .../Set-SqlDscRSVirtualDirectory.Tests.ps1 | 249 ++++++++++ 15 files changed, 1331 insertions(+), 565 deletions(-) create mode 100644 source/Public/Get-SqlDscRSVersion.ps1 create mode 100644 source/Public/Get-SqlDscRSWebPortalApplicationName.ps1 create mode 100644 source/Public/Set-SqlDscRSVirtualDirectory.ps1 create mode 100644 tests/Integration/Commands/Set-SqlDscRSVirtualDirectory.Integration.Tests.ps1 create mode 100644 tests/Unit/Public/Get-SqlDscRSVersion.Tests.ps1 create mode 100644 tests/Unit/Public/Get-SqlDscRSWebPortalApplicationName.Tests.ps1 create mode 100644 tests/Unit/Public/Set-SqlDscRSVirtualDirectory.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index dd4d7de324..87c956f098 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added public command `Set-SqlDscRSVirtualDirectory` to set the virtual directory + for Reporting Services applications. Wraps the `SetVirtualDirectory` CIM method + and supports ReportServerWebService, ReportServerWebApp, and ReportManager + applications ([issue #2015](https://github.com/dsccommunity/SqlServerDsc/issues/2015)). - Added public commands `Get-SqlDscRSUrlReservation`, `Add-SqlDscRSUrlReservation`, `Remove-SqlDscRSUrlReservation`, and `Set-SqlDscRSUrlReservation` to manage URL reservations for SQL Server Reporting Services or Power BI Report Server. @@ -22,6 +26,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Services version or explicit version specification. The returned CIM instance can be piped to `Enable-SqlDscRsSecureConnection` or `Disable-SqlDscRsSecureConnection` ([issue #2022](https://github.com/dsccommunity/SqlServerDsc/issues/2022)). +- Added public command `Get-SqlDscRSWebPortalApplicationName` to get the + Reporting Services web portal application name based on the SQL Server + version. Returns 'ReportServerWebApp' for SQL Server 2016 (version 13) and + later, or 'ReportManager' for earlier versions. Accepts the setup configuration + object from `Get-SqlDscRSSetupConfiguration` via pipeline. - Added public command `Enable-SqlDscRsSecureConnection` to enable secure connection for SQL Server Reporting Services or Power BI Report Server by setting the secure connection level to 1. Accepts the configuration CIM @@ -140,6 +149,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactored to use the public commands `Enable-SqlDscRsSecureConnection` and `Disable-SqlDscRsSecureConnection` for setting the secure connection level instead of calling the CIM method directly. + - Refactored to use the public commands `Get-SqlDscRSUrlReservation`, + `Add-SqlDscRSUrlReservation`, `Remove-SqlDscRSUrlReservation`, and + `Set-SqlDscRSUrlReservation` for managing URL reservations instead of calling + the CIM methods directly. - `Assert-SetupActionProperties` - Refactored to use the command `Get-FileVersion` from the DscResource.Common module instead of the private function `Get-FileVersionInformation` diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0cba919663..d3d624e74d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -539,6 +539,7 @@ stages: 'tests/Integration/Commands/Get-SqlDscRSConfiguration.Integration.Tests.ps1' 'tests/Integration/Commands/Enable-SqlDscRsSecureConnection.Integration.Tests.ps1' 'tests/Integration/Commands/Disable-SqlDscRsSecureConnection.Integration.Tests.ps1' + 'tests/Integration/Commands/Set-SqlDscRSVirtualDirectory.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1' 'tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1' 'tests/Integration/Commands/Remove-SqlDscRSUrlReservation.Integration.Tests.ps1' @@ -613,6 +614,7 @@ stages: 'tests/Integration/Commands/Get-SqlDscRSConfiguration.Integration.Tests.ps1' 'tests/Integration/Commands/Enable-SqlDscRsSecureConnection.Integration.Tests.ps1' 'tests/Integration/Commands/Disable-SqlDscRsSecureConnection.Integration.Tests.ps1' + 'tests/Integration/Commands/Set-SqlDscRSVirtualDirectory.Integration.Tests.ps1' 'tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1' 'tests/Integration/Commands/Add-SqlDscRSUrlReservation.Integration.Tests.ps1' 'tests/Integration/Commands/Remove-SqlDscRSUrlReservation.Integration.Tests.ps1' diff --git a/source/DSCResources/DSC_SqlRS/DSC_SqlRS.psm1 b/source/DSCResources/DSC_SqlRS/DSC_SqlRS.psm1 index 49a0b0e789..411e0cb426 100644 --- a/source/DSCResources/DSC_SqlRS/DSC_SqlRS.psm1 +++ b/source/DSCResources/DSC_SqlRS/DSC_SqlRS.psm1 @@ -25,7 +25,7 @@ $script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' #> function Get-TargetResource { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification = 'Neither command is needed for this function since it uses CIM methods when calling Get-ReportingServicesData')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification = 'Neither command is needed for this function since it uses CIM methods when calling Get-SqlDscRSConfiguration')] [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param @@ -65,28 +65,28 @@ function Get-TargetResource Encrypt = $Encrypt } - $reportingServicesData = Get-ReportingServicesData -InstanceName $InstanceName + $rsConfiguration = Get-SqlDscRSConfiguration -InstanceName $InstanceName -ErrorAction 'SilentlyContinue' - if ( $null -ne $reportingServicesData.Configuration ) + if ($null -ne $rsConfiguration) { - if ( $reportingServicesData.Configuration.DatabaseServerName.Contains('\') ) + if ($rsConfiguration.DatabaseServerName.Contains('\')) { - $getTargetResourceResult.DatabaseServerName = $reportingServicesData.Configuration.DatabaseServerName.Split('\')[0] - $getTargetResourceResult.DatabaseInstanceName = $reportingServicesData.Configuration.DatabaseServerName.Split('\')[1] + $getTargetResourceResult.DatabaseServerName = $rsConfiguration.DatabaseServerName.Split('\')[0] + $getTargetResourceResult.DatabaseInstanceName = $rsConfiguration.DatabaseServerName.Split('\')[1] } else { - $getTargetResourceResult.DatabaseServerName = $reportingServicesData.Configuration.DatabaseServerName + $getTargetResourceResult.DatabaseServerName = $rsConfiguration.DatabaseServerName $getTargetResourceResult.DatabaseInstanceName = 'MSSQLSERVER' } - $isInitialized = $reportingServicesData.Configuration.IsInitialized + $isInitialized = $rsConfiguration.IsInitialized [System.Boolean] $getTargetResourceResult.IsInitialized = $isInitialized - if ( $isInitialized ) + if ($isInitialized) { - if ( $reportingServicesData.Configuration.SecureConnectionLevel ) + if ($rsConfiguration.SecureConnectionLevel) { $getTargetResourceResult.UseSsl = $true } @@ -95,27 +95,25 @@ function Get-TargetResource $getTargetResourceResult.UseSsl = $false } - $getTargetResourceResult.ReportServerVirtualDirectory = $reportingServicesData.Configuration.VirtualDirectoryReportServer - $getTargetResourceResult.ReportsVirtualDirectory = $reportingServicesData.Configuration.VirtualDirectoryReportManager + $getTargetResourceResult.ReportServerVirtualDirectory = $rsConfiguration.VirtualDirectoryReportServer + $getTargetResourceResult.ReportsVirtualDirectory = $rsConfiguration.VirtualDirectoryReportManager - $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration - MethodName = 'ListReservedUrls' - } - - $reservedUrls = Invoke-RsCimMethod @invokeRsCimMethodParameters + $reservedUrls = $rsConfiguration | Get-SqlDscRSUrlReservation -ErrorAction 'SilentlyContinue' $reportServerReservedUrl = @() $reportsReservedUrl = @() - for ( $i = 0; $i -lt $reservedUrls.Application.Count; ++$i ) + $rsSetupConfiguration = Get-SqlDscRSSetupConfiguration -InstanceName $InstanceName -ErrorAction 'SilentlyContinue' + $reportsApplicationName = $rsSetupConfiguration | Get-SqlDscRSWebPortalApplicationName -ErrorAction 'SilentlyContinue' + + for ($i = 0; $i -lt $reservedUrls.Application.Count; ++$i) { - if ( $reservedUrls.Application[$i] -eq 'ReportServerWebService' ) + if ($reservedUrls.Application[$i] -eq 'ReportServerWebService') { $reportServerReservedUrl += $reservedUrls.UrlString[$i] } - if ( $reservedUrls.Application[$i] -eq $reportingServicesData.ReportsApplicationName ) + if ($reservedUrls.Application[$i] -eq $reportsApplicationName) { $reportsReservedUrl += $reservedUrls.UrlString[$i] } @@ -128,7 +126,7 @@ function Get-TargetResource { <# Make sure the value returned is false, if the value returned was - either empty, $null or $false. Fic for issue #822. + either empty, $null or $false. Fix for issue #822. #> [System.Boolean] $getTargetResourceResult.IsInitialized = $false } @@ -206,14 +204,13 @@ function Get-TargetResource (Get-CimClass @getCimClassParameters).CimClassMethods[$methodName].Parameters ``` - Or run the following using the helper function in this code. Make sure - to have the helper function loaded in the session. + Or run the following using the Get-SqlDscRSConfiguration command. ``` $methodName = 'ReserveUrl' $instanceName = 'SQL2016' - $reportingServicesData = Get-ReportingServicesData -InstanceName $InstanceName - $reportingServicesData.Configuration.CimClass.CimClassMethods[$methodName].Parameters + $rsConfiguration = Get-SqlDscRSConfiguration -InstanceName $InstanceName + $rsConfiguration.CimClass.CimClassMethods[$methodName].Parameters ``` SecureConnectionLevel (the parameter UseSsl): @@ -279,25 +276,29 @@ function Set-TargetResource $Encrypt ) - $reportingServicesData = Get-ReportingServicesData -InstanceName $InstanceName + $rsConfiguration = Get-SqlDscRSConfiguration -InstanceName $InstanceName -ErrorAction 'SilentlyContinue' - if ( $null -ne $reportingServicesData.Configuration ) + if ($null -ne $rsConfiguration) { - if ( $reportingServicesData.SqlVersion -ge 14 ) + $rsSetupConfiguration = Get-SqlDscRSSetupConfiguration -InstanceName $InstanceName -ErrorAction 'SilentlyContinue' + $sqlVersion = $rsSetupConfiguration | Get-SqlDscRSVersion -ErrorAction 'SilentlyContinue' + $reportsApplicationName = $rsSetupConfiguration | Get-SqlDscRSWebPortalApplicationName -ErrorAction 'SilentlyContinue' + + if ($sqlVersion.Major -ge 14) { - if ( [string]::IsNullOrEmpty($ReportServerVirtualDirectory) ) + if ([System.String]::IsNullOrEmpty($ReportServerVirtualDirectory)) { $ReportServerVirtualDirectory = 'ReportServer' } - if ( [string]::IsNullOrEmpty($ReportsVirtualDirectory) ) + if ([System.String]::IsNullOrEmpty($ReportsVirtualDirectory)) { $ReportsVirtualDirectory = 'Reports' } - $reportingServicesServiceName = $reportingServicesData.Configuration.ServiceName + $reportingServicesServiceName = $rsConfiguration.ServiceName - if ( [System.String]::IsNullOrEmpty($reportingServicesServiceName) ) + if ([System.String]::IsNullOrEmpty($reportingServicesServiceName)) { $errorMessage = $script:localizedData.ServiceNameIsNullOrEmpty -f $InstanceName @@ -306,7 +307,7 @@ function Set-TargetResource $reportingServicesDatabaseName = 'ReportServer' } - elseif ( $InstanceName -eq 'MSSQLSERVER' ) + elseif ($InstanceName -eq 'MSSQLSERVER') { if ( [System.String]::IsNullOrEmpty($ReportServerVirtualDirectory) ) { @@ -347,7 +348,7 @@ function Set-TargetResource $language = $wmiOperatingSystem.OSLanguage $restartReportingService = $false - if ( -not $reportingServicesData.Configuration.IsInitialized ) + if (-not $rsConfiguration.IsInitialized) { Write-Verbose -Message "Initializing Reporting Services on $DatabaseServerName\$DatabaseInstanceName." @@ -366,13 +367,13 @@ function Set-TargetResource $ReportsReservedUrl = @('http://+:80') } - if ( $reportingServicesData.Configuration.VirtualDirectoryReportServer -ne $ReportServerVirtualDirectory ) + if ($rsConfiguration.VirtualDirectoryReportServer -ne $ReportServerVirtualDirectory) { Write-Verbose -Message "Setting report server virtual directory on $DatabaseServerName\$DatabaseInstanceName to '$ReportServerVirtualDirectory'." # cSpell: ignore Lcid $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration + CimInstance = $rsConfiguration MethodName = 'SetVirtualDirectory' Arguments = @{ Application = 'ReportServerWebService' @@ -386,29 +387,19 @@ function Set-TargetResource $ReportServerReservedUrl | ForEach-Object -Process { Write-Verbose -Message "Adding report server URL reservation on $DatabaseServerName\$DatabaseInstanceName`: $_." - $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration - MethodName = 'ReserveUrl' - Arguments = @{ - Application = 'ReportServerWebService' - UrlString = $_ - Lcid = $language - } - } - - Invoke-RsCimMethod @invokeRsCimMethodParameters + $rsConfiguration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $_ -Lcid $language -Force -ErrorAction 'Stop' } } - if ( $reportingServicesData.Configuration.VirtualDirectoryReportManager -ne $ReportsVirtualDirectory ) + if ($rsConfiguration.VirtualDirectoryReportManager -ne $ReportsVirtualDirectory) { Write-Verbose -Message "Setting reports virtual directory on $DatabaseServerName\$DatabaseInstanceName to '$ReportServerVirtualDirectory'." $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration + CimInstance = $rsConfiguration MethodName = 'SetVirtualDirectory' Arguments = @{ - Application = $reportingServicesData.ReportsApplicationName + Application = $reportsApplicationName VirtualDirectory = $ReportsVirtualDirectory Lcid = $language } @@ -419,24 +410,14 @@ function Set-TargetResource $ReportsReservedUrl | ForEach-Object -Process { Write-Verbose -Message "Adding reports URL reservation on $DatabaseServerName\$DatabaseInstanceName`: $_." - $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration - MethodName = 'ReserveUrl' - Arguments = @{ - Application = $reportingServicesData.ReportsApplicationName - UrlString = $_ - Lcid = $language - } - } - - Invoke-RsCimMethod @invokeRsCimMethodParameters + $rsConfiguration | Add-SqlDscRSUrlReservation -Application $reportsApplicationName -UrlString $_ -Lcid $language -Force -ErrorAction 'Stop' } } Write-Verbose -Message "Generate database creation script on $DatabaseServerName\$DatabaseInstanceName for database '$reportingServicesDatabaseName'." $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration + CimInstance = $rsConfiguration MethodName = 'GenerateDatabaseCreationScript' Arguments = @{ DatabaseName = $reportingServicesDatabaseName @@ -445,7 +426,7 @@ function Set-TargetResource } } - $reportingServicesDatabaseScript = Invoke-RsCimMethod @invokeRsCimMethodParameters + $reportingServicesDatabaseScript = Invoke-RsCimMethod @invokeRsCimMethodParameters -ErrorAction 'Stop' # Determine RS service account $reportingServicesServiceAccountUserName = (Get-CimInstance -ClassName Win32_Service | Where-Object -FilterScript { @@ -455,7 +436,7 @@ function Set-TargetResource Write-Verbose -Message "Generate database rights script on $DatabaseServerName\$DatabaseInstanceName for database '$reportingServicesDatabaseName' and user '$reportingServicesServiceAccountUserName'." $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration + CimInstance = $rsConfiguration MethodName = 'GenerateDatabaseRightsScript' Arguments = @{ DatabaseName = $reportingServicesDatabaseName @@ -465,7 +446,7 @@ function Set-TargetResource } } - $reportingServicesDatabaseRightsScript = Invoke-RsCimMethod @invokeRsCimMethodParameters + $reportingServicesDatabaseRightsScript = Invoke-RsCimMethod @invokeRsCimMethodParameters -ErrorAction 'Stop' Import-SqlDscPreferredModule @@ -498,7 +479,7 @@ function Set-TargetResource } $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration + CimInstance = $rsConfiguration MethodName = 'SetDatabaseConnection' Arguments = @{ Server = $reportingServicesConnection @@ -525,7 +506,7 @@ function Set-TargetResource } } - Invoke-RsCimMethod @invokeRsCimMethodParameters + Invoke-RsCimMethod @invokeRsCimMethodParameters -ErrorAction 'Stop' <# When initializing SSRS 2019, the call to InitializeReportServer @@ -558,7 +539,8 @@ function Set-TargetResource $restartReportingService = $false - $reportingServicesData = Get-ReportingServicesData -InstanceName $InstanceName + # Refresh the configuration after restart + $rsConfiguration = Get-SqlDscRSConfiguration -InstanceName $InstanceName -ErrorAction 'Stop' <# Only execute InitializeReportServer if SetDatabaseConnection hasn't @@ -566,9 +548,9 @@ function Set-TargetResource InitializeReportServer will fail on SQL Server Standard and lower editions. #> - if ( -not $reportingServicesData.Configuration.IsInitialized ) + if (-not $rsConfiguration.IsInitialized) { - Write-Verbose -Message "Did not help restarting the Reporting Services service, running the CIM method to initialize report server on $DatabaseServerName\$DatabaseInstanceName for instance ID '$($reportingServicesData.Configuration.InstallationID)'." + Write-Verbose -Message "Did not help restarting the Reporting Services service, running the CIM method to initialize report server on $DatabaseServerName\$DatabaseInstanceName for instance ID '$($rsConfiguration.InstallationID)'." <# Add an additional wait before calling InitializeReportServer to give @@ -584,21 +566,21 @@ function Set-TargetResource $restartReportingService = $true $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration + CimInstance = $rsConfiguration MethodName = 'InitializeReportServer' Arguments = @{ - InstallationId = $reportingServicesData.Configuration.InstallationID + InstallationId = $rsConfiguration.InstallationID } } - Invoke-RsCimMethod @invokeRsCimMethodParameters + Invoke-RsCimMethod @invokeRsCimMethodParameters -ErrorAction 'Stop' } else { Write-Verbose -Message "Reporting Services on $DatabaseServerName\$DatabaseInstanceName is initialized." } - if ( $PSBoundParameters.ContainsKey('UseSsl') -and $UseSsl -ne $reportingServicesData.Configuration.SecureConnectionLevel ) + if ( $PSBoundParameters.ContainsKey('UseSsl') -and $UseSsl -ne $rsConfiguration.SecureConnectionLevel ) { Write-Verbose -Message "Changing value for using SSL to '$UseSsl'." @@ -606,11 +588,11 @@ function Set-TargetResource if ($UseSsl) { - $reportingServicesData.Configuration | Enable-SqlDscRsSecureConnection -Force + $rsConfiguration | Enable-SqlDscRsSecureConnection -Force -ErrorAction 'Stop' } else { - $reportingServicesData.Configuration | Disable-SqlDscRsSecureConnection -Force + $rsConfiguration | Disable-SqlDscRsSecureConnection -Force -ErrorAction 'Stop' } } } @@ -652,21 +634,11 @@ function Set-TargetResource $restartReportingService = $true $currentConfig.ReportServerReservedUrl | ForEach-Object -Process { - $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration - MethodName = 'RemoveURL' - Arguments = @{ - Application = 'ReportServerWebService' - UrlString = $_ - Lcid = $language - } - } - - Invoke-RsCimMethod @invokeRsCimMethodParameters + $rsConfiguration | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $_ -Lcid $language -Force -ErrorAction 'Stop' } $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration + CimInstance = $rsConfiguration MethodName = 'SetVirtualDirectory' Arguments = @{ Application = 'ReportServerWebService' @@ -675,20 +647,10 @@ function Set-TargetResource } } - Invoke-RsCimMethod @invokeRsCimMethodParameters + Invoke-RsCimMethod @invokeRsCimMethodParameters -ErrorAction 'Stop' $currentConfig.ReportServerReservedUrl | ForEach-Object -Process { - $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration - MethodName = 'ReserveUrl' - Arguments = @{ - Application = 'ReportServerWebService' - UrlString = $_ - Lcid = $language - } - } - - Invoke-RsCimMethod @invokeRsCimMethodParameters + $rsConfiguration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $_ -Lcid $language -Force -ErrorAction 'Stop' } } @@ -699,43 +661,23 @@ function Set-TargetResource $restartReportingService = $true $currentConfig.ReportsReservedUrl | ForEach-Object -Process { - $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration - MethodName = 'RemoveURL' - Arguments = @{ - Application = $reportingServicesData.ReportsApplicationName - UrlString = $_ - Lcid = $language - } - } - - Invoke-RsCimMethod @invokeRsCimMethodParameters + $rsConfiguration | Remove-SqlDscRSUrlReservation -Application $reportsApplicationName -UrlString $_ -Lcid $language -Force -ErrorAction 'Stop' } $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration + CimInstance = $rsConfiguration MethodName = 'SetVirtualDirectory' Arguments = @{ - Application = $reportingServicesData.ReportsApplicationName + Application = $reportsApplicationName VirtualDirectory = $ReportsVirtualDirectory Lcid = $language } } - Invoke-RsCimMethod @invokeRsCimMethodParameters + Invoke-RsCimMethod @invokeRsCimMethodParameters -ErrorAction 'Stop' $currentConfig.ReportsReservedUrl | ForEach-Object -Process { - $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration - MethodName = 'ReserveUrl' - Arguments = @{ - Application = $reportingServicesData.ReportsApplicationName - UrlString = $_ - Lcid = $language - } - } - - Invoke-RsCimMethod @invokeRsCimMethodParameters + $rsConfiguration | Add-SqlDscRSUrlReservation -Application $reportsApplicationName -UrlString $_ -Lcid $language -Force -ErrorAction 'Stop' } } @@ -746,36 +688,11 @@ function Set-TargetResource if ( ($null -ne $ReportServerReservedUrl) -and ($null -ne (Compare-Object @compareParameters)) ) { - $restartReportingService = $true - - $currentConfig.ReportServerReservedUrl | ForEach-Object -Process { - $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration - MethodName = 'RemoveURL' - Arguments = @{ - Application = 'ReportServerWebService' - UrlString = $_ - Lcid = $language - } - } - - Invoke-RsCimMethod @invokeRsCimMethodParameters - } + Write-Verbose -Message "Updating report server URL reservations on $DatabaseServerName\$DatabaseInstanceName." - $ReportServerReservedUrl | ForEach-Object -Process { - Write-Verbose -Message "Adding report server URL reservation on $DatabaseServerName\$DatabaseInstanceName`: $_." - $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration - MethodName = 'ReserveUrl' - Arguments = @{ - Application = 'ReportServerWebService' - UrlString = $_ - Lcid = $language - } - } + $restartReportingService = $true - Invoke-RsCimMethod @invokeRsCimMethodParameters - } + $rsConfiguration | Set-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $ReportServerReservedUrl -Lcid $language -Force -ErrorAction 'Stop' } $compareParameters = @{ @@ -785,37 +702,11 @@ function Set-TargetResource if ( ($null -ne $ReportsReservedUrl) -and ($null -ne (Compare-Object @compareParameters)) ) { - $restartReportingService = $true - - $currentConfig.ReportsReservedUrl | ForEach-Object -Process { - $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration - MethodName = 'RemoveURL' - Arguments = @{ - Application = $reportingServicesData.ReportsApplicationName - UrlString = $_ - Lcid = $language - } - } - - Invoke-RsCimMethod @invokeRsCimMethodParameters - } - - $ReportsReservedUrl | ForEach-Object -Process { - Write-Verbose -Message "Adding reports URL reservation on $DatabaseServerName\$DatabaseInstanceName`: $_." + Write-Verbose -Message "Updating reports URL reservations on $DatabaseServerName\$DatabaseInstanceName." - $invokeRsCimMethodParameters = @{ - CimInstance = $reportingServicesData.Configuration - MethodName = 'ReserveUrl' - Arguments = @{ - Application = $reportingServicesData.ReportsApplicationName - UrlString = $_ - Lcid = $language - } - } + $restartReportingService = $true - Invoke-RsCimMethod @invokeRsCimMethodParameters - } + $rsConfiguration | Set-SqlDscRSUrlReservation -Application $reportsApplicationName -UrlString $ReportsReservedUrl -Lcid $language -Force -ErrorAction 'Stop' } if ( $PSBoundParameters.ContainsKey('UseSsl') -and $UseSsl -ne $currentConfig.UseSsl ) @@ -826,11 +717,11 @@ function Set-TargetResource if ($UseSsl) { - $reportingServicesData.Configuration | Enable-SqlDscRsSecureConnection -Force + $rsConfiguration | Enable-SqlDscRsSecureConnection -Force -ErrorAction 'Stop' } else { - $reportingServicesData.Configuration | Disable-SqlDscRsSecureConnection -Force + $rsConfiguration | Disable-SqlDscRsSecureConnection -Force -ErrorAction 'Stop' } } } @@ -1047,68 +938,6 @@ function Test-TargetResource $result } -<# - .SYNOPSIS - Returns SQL Reporting Services data: configuration object used to initialize and configure - SQL Reporting Services and the name of the Reports Web application name (changed in SQL 2016) - - .PARAMETER InstanceName - Name of the SQL Server Reporting Services instance for which the data is being retrieved. -#> -function Get-ReportingServicesData -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName - ) - - $instanceNamesRegistryKey = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\RS' - - if (Get-ItemProperty -Path $instanceNamesRegistryKey -Name $InstanceName -ErrorAction SilentlyContinue) - { - $instanceId = (Get-ItemProperty -Path $instanceNamesRegistryKey -Name $InstanceName).$InstanceName - - if (Test-Path -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$instanceId\MSSQLServer\CurrentVersion") - { - # SQL Server 2017 and 2019 SSRS stores current SQL Server version to a different Registry path. - $sqlVersion = [int]((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$InstanceId\MSSQLServer\CurrentVersion" -Name 'CurrentVersion').CurrentVersion).Split('.')[0] - } - else - { - $sqlVersion = [int]((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$instanceId\Setup" -Name 'Version').Version).Split('.')[0] - } - - $reportingServicesConfiguration = Get-CimInstance -ClassName MSReportServer_ConfigurationSetting -Namespace "root\Microsoft\SQLServer\ReportServer\RS_$InstanceName\v$sqlVersion\Admin" - $reportingServicesConfiguration = $reportingServicesConfiguration | Where-Object -FilterScript { - $_.InstanceName -eq $InstanceName - } - - <# - SQL Server Reporting Services Web Portal application name changed - in SQL Server 2016. - https://docs.microsoft.com/en-us/sql/reporting-services/breaking-changes-in-sql-server-reporting-services-in-sql-server-2016 - #> - if ($sqlVersion -ge 13) - { - $reportsApplicationName = 'ReportServerWebApp' - } - else - { - $reportsApplicationName = 'ReportManager' - } - } - - @{ - Configuration = $reportingServicesConfiguration - ReportsApplicationName = $reportsApplicationName - SqlVersion = $sqlVersion - } -} - <# .SYNOPSIS A wrapper for Invoke-CimMethod to be able to handle errors in one place. diff --git a/source/Private/Get-OperatingSystem.ps1 b/source/Private/Get-OperatingSystem.ps1 index 4af976719d..956601be7f 100644 --- a/source/Private/Get-OperatingSystem.ps1 +++ b/source/Private/Get-OperatingSystem.ps1 @@ -38,11 +38,9 @@ function Get-OperatingSystem if ($null -eq $wmiOperatingSystem) { - $errorMessage = $script:localizedData.Get_OperatingSystem_FailedToGet + Write-Error -Message $script:localizedData.Get_OperatingSystem_FailedToGet -Category 'ObjectNotFound' -ErrorId 'GOS0001' -TargetObject $null - $errorRecord = New-ErrorRecord -Exception (New-InvalidOperationException -Message $errorMessage -PassThru) -ErrorId 'GOS0001' -ErrorCategory 'ObjectNotFound' -TargetObject $null - - $PSCmdlet.ThrowTerminatingError($errorRecord) + return } return $wmiOperatingSystem diff --git a/source/Public/Get-SqlDscRSUrlReservation.ps1 b/source/Public/Get-SqlDscRSUrlReservation.ps1 index 90f9c9f030..ff50ef88e3 100644 --- a/source/Public/Get-SqlDscRSUrlReservation.ps1 +++ b/source/Public/Get-SqlDscRSUrlReservation.ps1 @@ -80,9 +80,9 @@ function Get-SqlDscRSUrlReservation { $errorMessage = $script:localizedData.Get_SqlDscRSUrlReservation_FailedToGet -f $instanceName, $_.Exception.Message - $errorRecord = New-ErrorRecord -Exception (New-InvalidOperationException -Message $errorMessage -PassThru) -ErrorId 'GSRUR0001' -ErrorCategory 'InvalidOperation' -TargetObject $Configuration + Write-Error -Message $errorMessage -Category 'InvalidOperation' -ErrorId 'GSRUR0001' -TargetObject $Configuration -Exception $_.Exception - $PSCmdlet.ThrowTerminatingError($errorRecord) + return } return $result diff --git a/source/Public/Get-SqlDscRSVersion.ps1 b/source/Public/Get-SqlDscRSVersion.ps1 new file mode 100644 index 0000000000..d0bfb1ca7e --- /dev/null +++ b/source/Public/Get-SqlDscRSVersion.ps1 @@ -0,0 +1,56 @@ +<# + .SYNOPSIS + Gets the SQL Server Reporting Services version. + + .DESCRIPTION + Gets the SQL Server Reporting Services version from the setup configuration. + This is used to determine version-specific behavior. + + The setup configuration object can be obtained using the + `Get-SqlDscRSSetupConfiguration` command and passed via the pipeline. + + .PARAMETER Configuration + Specifies the setup configuration object for the Reporting Services instance. + This can be obtained using the `Get-SqlDscRSSetupConfiguration` command. + This parameter accepts pipeline input. + + .EXAMPLE + Get-SqlDscRSSetupConfiguration -InstanceName 'SSRS' | Get-SqlDscRSVersion + + Returns the version for the SSRS instance (e.g. 15.0.1100.0 for SQL 2019). + + .INPUTS + `System.Management.Automation.PSCustomObject` + + Accepts setup configuration object via pipeline. + + .OUTPUTS + `System.Version` + + Returns the version of the Reporting Services instance. +#> +function Get-SqlDscRSVersion +{ + [CmdletBinding()] + [OutputType([System.Version])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Object] + $Configuration + ) + + process + { + if ([System.String]::IsNullOrEmpty($Configuration.CurrentVersion)) + { + Write-Error -Message $script:localizedData.Get_SqlDscRSVersion_VersionNotFound -Category 'ObjectNotFound' -ErrorId 'GSRSV0001' -TargetObject $Configuration + + return + } + + $version = [System.Version] $Configuration.CurrentVersion + + return $version + } +} diff --git a/source/Public/Get-SqlDscRSWebPortalApplicationName.ps1 b/source/Public/Get-SqlDscRSWebPortalApplicationName.ps1 new file mode 100644 index 0000000000..18b1d50dde --- /dev/null +++ b/source/Public/Get-SqlDscRSWebPortalApplicationName.ps1 @@ -0,0 +1,79 @@ +<# + .SYNOPSIS + Gets the Reporting Services web portal application name based on version. + + .DESCRIPTION + Gets the Reporting Services web portal application name based on the SQL + Server major version. SQL Server 2016 (version 13) and later use + 'ReportServerWebApp', while earlier versions use 'ReportManager'. + + The setup configuration object can be obtained using the + `Get-SqlDscRSSetupConfiguration` command and passed via the pipeline. + + See more information at: + https://docs.microsoft.com/en-us/sql/reporting-services/breaking-changes-in-sql-server-reporting-services-in-sql-server-2016 + + .PARAMETER Configuration + Specifies the setup configuration object for the Reporting Services instance. + This can be obtained using the `Get-SqlDscRSSetupConfiguration` command. + This parameter accepts pipeline input. + + .EXAMPLE + Get-SqlDscRSSetupConfiguration -InstanceName 'SSRS' | Get-SqlDscRSWebPortalApplicationName + + Returns 'ReportServerWebApp' or 'ReportManager' based on the SQL Server + version, using pipeline input from the setup configuration object. + + .INPUTS + `System.Management.Automation.PSCustomObject` + + Accepts setup configuration object via pipeline. + + .OUTPUTS + `System.String` + + Returns either 'ReportServerWebApp' for SQL Server 2016 and later, + or 'ReportManager' for earlier versions. +#> +function Get-SqlDscRSWebPortalApplicationName +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Object] + $Configuration + ) + + process + { + Write-Debug -Message $script:localizedData.Get_SqlDscRSWebPortalApplicationName_GettingApplicationName + + $sqlVersion = $Configuration | Get-SqlDscRSVersion + + if (-not $sqlVersion) + { + return + } + + $sqlMajorVersion = $sqlVersion.Major + + <# + SQL Server Reporting Services Web Portal application name changed + in SQL Server 2016 (version 13). + #> + if ($sqlMajorVersion -ge 13) + { + $reportsApplicationName = 'ReportServerWebApp' + } + else + { + $reportsApplicationName = 'ReportManager' + } + + Write-Debug -Message ($script:localizedData.Get_SqlDscRSWebPortalApplicationName_ApplicationName -f $reportsApplicationName) + + return $reportsApplicationName + } +} diff --git a/source/Public/Set-SqlDscRSVirtualDirectory.ps1 b/source/Public/Set-SqlDscRSVirtualDirectory.ps1 new file mode 100644 index 0000000000..1581c7db93 --- /dev/null +++ b/source/Public/Set-SqlDscRSVirtualDirectory.ps1 @@ -0,0 +1,175 @@ +<# + .SYNOPSIS + Sets the virtual directory for SQL Server Reporting Services. + + .DESCRIPTION + Sets the virtual directory for SQL Server Reporting Services or + Power BI Report Server by calling the `SetVirtualDirectory` method on + the `MSReportServer_ConfigurationSetting` CIM instance. + + This command must be called before URL reservations can be added for + a Reporting Services application. The virtual directory defines the + path segment in the URL used to access the application. + + 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 virtual directory. + 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 VirtualDirectory + Specifies the virtual directory name. This is the path segment used + in the URL to access the application. Common values are 'ReportServer' + for the web service and 'Reports' for the web portal. + + .PARAMETER Lcid + Specifies the language code identifier (LCID) for the virtual directory. + 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 virtual directory. + + .PARAMETER Force + If specified, suppresses the confirmation prompt. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' + + Sets the virtual directory for the Report Server Web Service to + 'ReportServer'. + + .EXAMPLE + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' + Set-SqlDscRSVirtualDirectory -Configuration $config -Application 'ReportServerWebApp' -VirtualDirectory 'Reports' -Confirm:$false + + Sets the virtual directory for the Reports web application to 'Reports' + without confirmation. + + .EXAMPLE + Get-SqlDscRSConfiguration -InstanceName 'SSRS' | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -PassThru + + Sets the virtual directory 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 virtual directory must be set before URL reservations can be added + for the application. After setting the virtual directory, use + `Add-SqlDscRSUrlReservation` to add URL reservations. + + 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-setvirtualdirectory +#> +function Set-SqlDscRSVirtualDirectory +{ + # cSpell: ignore PBIRS Lcid + [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] + $VirtualDirectory, + + [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.Set_SqlDscRSVirtualDirectory_Setting -f $VirtualDirectory, $Application, $instanceName) + + $descriptionMessage = $script:localizedData.Set_SqlDscRSVirtualDirectory_ShouldProcessDescription -f $VirtualDirectory, $Application, $instanceName + $confirmationMessage = $script:localizedData.Set_SqlDscRSVirtualDirectory_ShouldProcessConfirmation -f $VirtualDirectory, $Application + $captionMessage = $script:localizedData.Set_SqlDscRSVirtualDirectory_ShouldProcessCaption + + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) + { + $invokeRsCimMethodParameters = @{ + CimInstance = $Configuration + MethodName = 'SetVirtualDirectory' + Arguments = @{ + Application = $Application + VirtualDirectory = $VirtualDirectory + Lcid = $Lcid + } + } + + try + { + $null = Invoke-RsCimMethod @invokeRsCimMethodParameters + } + catch + { + $errorMessage = $script:localizedData.Set_SqlDscRSVirtualDirectory_FailedToSet -f $instanceName, $_.Exception.Message + + $errorRecord = New-ErrorRecord -Exception (New-InvalidOperationException -Message $errorMessage -PassThru) -ErrorId 'SSRSVD0001' -ErrorCategory 'InvalidOperation' -TargetObject $Configuration + + $PSCmdlet.ThrowTerminatingError($errorRecord) + } + } + + if ($PassThru.IsPresent) + { + return $Configuration + } + } +} diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 01c42f38be..f30f6e634b 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -296,6 +296,14 @@ ConvertFrom-StringData @' Get_SqlDscRSConfiguration_FailedToGetConfiguration = Failed to get the configuration CIM instance for Reporting Services instance '{0}': {1} (GSRSCD0003) Get_SqlDscRSConfiguration_ConfigurationNotFound = Could not find the configuration CIM instance for Reporting Services instance '{0}'. (GSRSCD0004) + ## Get-SqlDscRSWebPortalApplicationName + Get_SqlDscRSWebPortalApplicationName_GettingApplicationName = Getting web portal application name. + + Get_SqlDscRSWebPortalApplicationName_ApplicationName = Web portal application name is '{0}'. + + ## Get-SqlDscRSVersion + Get_SqlDscRSVersion_VersionNotFound = Could not determine the version. (GSRSV0001) + ## Enable-SqlDscRsSecureConnection Enable_SqlDscRsSecureConnection_Enabling = Enabling secure connection for Reporting Services instance '{0}'. Enable_SqlDscRsSecureConnection_ShouldProcessDescription = Enabling secure connection (secure connection level 1) for the Reporting Services instance '{0}'. @@ -740,4 +748,12 @@ ConvertFrom-StringData @' Set_SqlDscRSUrlReservation_DesiredUrls = Desired URL reservations for application '{0}': {1} Set_SqlDscRSUrlReservation_RemovingUrl = Removing URL reservation '{0}' for application '{1}' on Reporting Services instance '{2}'. Set_SqlDscRSUrlReservation_AddingUrl = Adding URL reservation '{0}' for application '{1}' on Reporting Services instance '{2}'. + + ## Set-SqlDscRSVirtualDirectory + Set_SqlDscRSVirtualDirectory_Setting = Setting virtual directory '{0}' for application '{1}' on Reporting Services instance '{2}'. + Set_SqlDscRSVirtualDirectory_ShouldProcessDescription = Setting virtual directory '{0}' for application '{1}' on Reporting Services instance '{2}'. + Set_SqlDscRSVirtualDirectory_ShouldProcessConfirmation = Are you sure you want to set virtual directory '{0}' for application '{1}'? + # This string shall not end with full stop (.) since it is used as a title of ShouldProcess messages. + Set_SqlDscRSVirtualDirectory_ShouldProcessCaption = Set virtual directory for Reporting Services instance + Set_SqlDscRSVirtualDirectory_FailedToSet = Failed to set virtual directory for Reporting Services instance '{0}'. {1} (SSRSVD0001) '@ diff --git a/tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1 b/tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1 index 6c9b2b6065..6a806e2d14 100644 --- a/tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Get-SqlDscRSUrlReservation.Integration.Tests.ps1 @@ -55,8 +55,14 @@ Describe 'Get-SqlDscRSUrlReservation' { # The result should be a CIM method result with URL reservation properties $result | Should -Not -BeNullOrEmpty $result.HRESULT | Should -Be 0 - $result.Application | Should -Not -BeNullOrEmpty - $result.UrlString | Should -Not -BeNullOrEmpty + + <# + Application and UrlString properties may be empty arrays if no URL + reservations exist (e.g., freshly installed but not initialized SSRS). + Just verify the properties exist. + #> + $result.PSObject.Properties.Name | Should -Contain 'Application' + $result.PSObject.Properties.Name | Should -Contain 'UrlString' } } @@ -83,8 +89,14 @@ Describe 'Get-SqlDscRSUrlReservation' { # The result should be a CIM method result with URL reservation properties $result | Should -Not -BeNullOrEmpty $result.HRESULT | Should -Be 0 - $result.Application | Should -Not -BeNullOrEmpty - $result.UrlString | Should -Not -BeNullOrEmpty + + <# + Application and UrlString properties may be empty arrays if no URL + reservations exist (e.g., freshly installed but not initialized SSRS). + Just verify the properties exist. + #> + $result.PSObject.Properties.Name | Should -Contain 'Application' + $result.PSObject.Properties.Name | Should -Contain 'UrlString' } } @@ -111,8 +123,14 @@ Describe 'Get-SqlDscRSUrlReservation' { # The result should be a CIM method result with URL reservation properties $result | Should -Not -BeNullOrEmpty $result.HRESULT | Should -Be 0 - $result.Application | Should -Not -BeNullOrEmpty - $result.UrlString | Should -Not -BeNullOrEmpty + + <# + Application and UrlString properties may be empty arrays if no URL + reservations exist (e.g., freshly installed but not initialized SSRS). + Just verify the properties exist. + #> + $result.PSObject.Properties.Name | Should -Contain 'Application' + $result.PSObject.Properties.Name | Should -Contain 'UrlString' } } @@ -140,8 +158,14 @@ Describe 'Get-SqlDscRSUrlReservation' { # The result should be a CIM method result with URL reservation properties $result | Should -Not -BeNullOrEmpty $result.HRESULT | Should -Be 0 - $result.Application | Should -Not -BeNullOrEmpty - $result.UrlString | Should -Not -BeNullOrEmpty + + <# + Application and UrlString properties may be empty arrays if no URL + reservations exist (e.g., freshly installed but not initialized PBIRS). + Just verify the properties exist. + #> + $result.PSObject.Properties.Name | Should -Contain 'Application' + $result.PSObject.Properties.Name | Should -Contain 'UrlString' } } } diff --git a/tests/Integration/Commands/Set-SqlDscRSVirtualDirectory.Integration.Tests.ps1 b/tests/Integration/Commands/Set-SqlDscRSVirtualDirectory.Integration.Tests.ps1 new file mode 100644 index 0000000000..c66f6103fe --- /dev/null +++ b/tests/Integration/Commands/Set-SqlDscRSVirtualDirectory.Integration.Tests.ps1 @@ -0,0 +1,123 @@ +[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 'Set-SqlDscRSVirtualDirectory' { + Context 'When setting virtual directory for SQL Server Reporting Services' -Tag @('Integration_SQL2017_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should set virtual directory for ReportServerWebService using pipeline' { + { $script:configuration | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Force -ErrorAction 'Stop' } | Should -Not -Throw + + # Verify the virtual directory was set + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + + $config.VirtualDirectoryReportServer | Should -Be 'ReportServer' + } + + It 'Should return configuration when using PassThru' { + $result = $script:configuration | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When setting virtual directory for SQL Server Reporting Services' -Tag @('Integration_SQL2019_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should set virtual directory for ReportServerWebService using pipeline' { + { $script:configuration | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Force -ErrorAction 'Stop' } | Should -Not -Throw + + # Verify the virtual directory was set + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + + $config.VirtualDirectoryReportServer | Should -Be 'ReportServer' + } + + It 'Should return configuration when using PassThru' { + $result = $script:configuration | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When setting virtual directory for SQL Server Reporting Services' -Tag @('Integration_SQL2022_RS') { + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + } + + It 'Should set virtual directory for ReportServerWebService using pipeline' { + { $script:configuration | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Force -ErrorAction 'Stop' } | Should -Not -Throw + + # Verify the virtual directory was set + $config = Get-SqlDscRSConfiguration -InstanceName 'SSRS' -ErrorAction 'Stop' + + $config.VirtualDirectoryReportServer | Should -Be 'ReportServer' + } + + It 'Should return configuration when using PassThru' { + $result = $script:configuration | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When setting virtual directory for Power BI Report Server' -Tag @('Integration_PowerBI') { + # cSpell: ignore PBIRS + BeforeAll { + $script:configuration = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + } + + It 'Should set virtual directory for ReportServerWebService using pipeline' { + { $script:configuration | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Force -ErrorAction 'Stop' } | Should -Not -Throw + + # Verify the virtual directory was set + $config = Get-SqlDscRSConfiguration -InstanceName 'PBIRS' -ErrorAction 'Stop' + + $config.VirtualDirectoryReportServer | Should -Be 'ReportServer' + } + + It 'Should return configuration when using PassThru' { + $result = $script:configuration | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Force -PassThru -ErrorAction 'Stop' + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'PBIRS' + } + } +} diff --git a/tests/Unit/DSC_SqlRS.Tests.ps1 b/tests/Unit/DSC_SqlRS.Tests.ps1 index e4344e547e..0650caae38 100644 --- a/tests/Unit/DSC_SqlRS.Tests.ps1 +++ b/tests/Unit/DSC_SqlRS.Tests.ps1 @@ -162,11 +162,18 @@ Describe 'SqlRS\Get-TargetResource' -Tag 'Get' { Context 'When the system is in the desired state' { BeforeAll { - Mock -CommandName Get-ReportingServicesData -MockWith { + Mock -CommandName Get-SqlDscRSConfiguration -MockWith { + return (& $mockGetCimInstance_ConfigurationSetting_NamedInstance)[0] + } + + Mock -CommandName Get-SqlDscRSWebPortalApplicationName -MockWith { + return 'ReportServerWebApp' + } + + Mock -CommandName Get-SqlDscRSSetupConfiguration -MockWith { return @{ - Configuration = (& $mockGetCimInstance_ConfigurationSetting_NamedInstance)[0] - ReportsApplicationName = 'ReportServerWebApp' - SqlVersion = 13 + InstanceName = $mockNamedInstanceName + CurrentVersion = '13.0.0.0' } } @@ -256,11 +263,18 @@ Describe 'SqlRS\Get-TargetResource' -Tag 'Get' { Context 'When the system is not in the desired state' { BeforeAll { - Mock -CommandName Get-ReportingServicesData -MockWith { + Mock -CommandName Get-SqlDscRSConfiguration -MockWith { + return (& $mockGetCimInstance_ConfigurationSetting_DefaultInstance)[0] + } + + Mock -CommandName Get-SqlDscRSWebPortalApplicationName -MockWith { + return 'ReportServerWebApp' + } + + Mock -CommandName Get-SqlDscRSSetupConfiguration -MockWith { return @{ - Configuration = (& $mockGetCimInstance_ConfigurationSetting_DefaultInstance)[0] - ReportsApplicationName = 'ReportServerWebApp' - SqlVersion = 13 + InstanceName = $mockDefaultInstanceName + CurrentVersion = '13.0.0.0' } } @@ -345,10 +359,8 @@ Describe 'SqlRS\Get-TargetResource' -Tag 'Get' { Context 'When there is no Reporting Services instance' { BeforeAll { - Mock -CommandName Get-ReportingServicesData -MockWith { - return @{ - Configuration = $null - } + Mock -CommandName Get-SqlDscRSConfiguration -MockWith { + return $null } } @@ -567,14 +579,25 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { BeforeAll { $mockDynamicIsInitialized = $false - Mock -CommandName Get-ReportingServicesData -MockWith { + Mock -CommandName Get-SqlDscRSConfiguration -MockWith { + return (& $mockGetCimInstance_ConfigurationSetting_NamedInstance)[0] + } + + Mock -CommandName Get-SqlDscRSWebPortalApplicationName -MockWith { + return 'ReportServerWebApp' + } + + Mock -CommandName Get-SqlDscRSSetupConfiguration -MockWith { return @{ - Configuration = (& $mockGetCimInstance_ConfigurationSetting_NamedInstance)[0] - ReportsApplicationName = 'ReportServerWebApp' - SqlVersion = $TestCaseVersion + InstanceName = $mockNamedInstanceName + CurrentVersion = "$TestCaseVersion.0.0.0" } } + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] "$TestCaseVersion.0.0.0" + } + Mock -CommandName Test-TargetResource -MockWith { return $true } @@ -715,14 +738,25 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { ) } - Mock -CommandName Get-ReportingServicesData -MockWith { + Mock -CommandName Get-SqlDscRSConfiguration -MockWith { + return (& $mockGetCimInstance_ConfigurationSetting_ServiceNameNull)[0] + } + + Mock -CommandName Get-SqlDscRSWebPortalApplicationName -MockWith { + return 'ReportServerWebApp' + } + + Mock -CommandName Get-SqlDscRSSetupConfiguration -MockWith { return @{ - Configuration = (& $mockGetCimInstance_ConfigurationSetting_ServiceNameNull)[0] - ReportsApplicationName = 'ReportServerWebApp' - SqlVersion = 14 + InstanceName = $mockNamedInstanceName + CurrentVersion = '14.0.0.0' } } + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] '14.0.0.0' + } + Mock -CommandName Get-CimInstance ` -MockWith $mockGetCimInstance_Language ` -ParameterFilter $mockGetCimInstance_OperatingSystem_ParameterFilter @@ -743,17 +777,30 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { } It 'Should throw the correct error message when ServiceName is empty' { - Mock -CommandName Get-ReportingServicesData -MockWith { - return @{ - Configuration = New-Object -TypeName Microsoft.Management.Infrastructure.CimInstance -ArgumentList @( + Mock -CommandName Get-SqlDscRSConfiguration -MockWith { + return ( + New-Object -TypeName Microsoft.Management.Infrastructure.CimInstance -ArgumentList @( 'MSReportServer_ConfigurationSetting' 'root/Microsoft/SQLServer/ReportServer/RS_SQL2016/v14/Admin' ) | Add-Member -MemberType NoteProperty -Name 'ServiceName' -Value '' -PassThru -Force - ReportsApplicationName = 'ReportServerWebApp' - SqlVersion = 14 + ) + } + + Mock -CommandName Get-SqlDscRSWebPortalApplicationName -MockWith { + return 'ReportServerWebApp' + } + + Mock -CommandName Get-SqlDscRSSetupConfiguration -MockWith { + return @{ + InstanceName = $mockNamedInstanceName + CurrentVersion = '14.0.0.0' } } + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] '14.0.0.0' + } + InModuleScope -ScriptBlock { Set-StrictMode -Version 1.0 @@ -773,14 +820,25 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { BeforeAll { $mockDynamicIsInitialized = $true - Mock -CommandName Get-ReportingServicesData -MockWith { + Mock -CommandName Get-SqlDscRSConfiguration -MockWith { + return (& $mockGetCimInstance_ConfigurationSetting_NamedInstance)[0] + } + + Mock -CommandName Get-SqlDscRSWebPortalApplicationName -MockWith { + return 'ReportServerWebApp' + } + + Mock -CommandName Get-SqlDscRSSetupConfiguration -MockWith { return @{ - Configuration = (& $mockGetCimInstance_ConfigurationSetting_NamedInstance)[0] - ReportsApplicationName = 'ReportServerWebApp' - SqlVersion = $TestCaseVersion + InstanceName = $mockNamedInstanceName + CurrentVersion = "$TestCaseVersion.0.0.0" } } + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] "$TestCaseVersion.0.0.0" + } + Mock -CommandName Get-TargetResource -MockWith { return @{ ReportServerReservedUrl = $mockReportServerApplicationUrl @@ -865,14 +923,25 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { BeforeAll { $mockDynamicIsInitialized = $true - Mock -CommandName Get-ReportingServicesData -MockWith { + Mock -CommandName Get-SqlDscRSConfiguration -MockWith { + return (& $mockGetCimInstance_ConfigurationSetting_NamedInstance)[0] + } + + Mock -CommandName Get-SqlDscRSWebPortalApplicationName -MockWith { + return 'ReportServerWebApp' + } + + Mock -CommandName Get-SqlDscRSSetupConfiguration -MockWith { return @{ - Configuration = (& $mockGetCimInstance_ConfigurationSetting_NamedInstance)[0] - ReportsApplicationName = 'ReportServerWebApp' - SqlVersion = $TestCaseVersion + InstanceName = $mockNamedInstanceName + CurrentVersion = "$TestCaseVersion.0.0.0" } } + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] "$TestCaseVersion.0.0.0" + } + Mock -CommandName Get-TargetResource -MockWith { return @{ ReportServerReservedUrl = $mockReportServerApplicationUrl @@ -973,14 +1042,25 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { BeforeEach { # This mocks the SQL Server Reporting Services 2014 and older - Mock -CommandName Get-ReportingServicesData -MockWith { + Mock -CommandName Get-SqlDscRSConfiguration -MockWith { + return (& $mockGetCimInstance_ConfigurationSetting_DefaultInstance)[0] + } + + Mock -CommandName Get-SqlDscRSWebPortalApplicationName -MockWith { + return 'ReportManager' + } + + Mock -CommandName Get-SqlDscRSSetupConfiguration -MockWith { return @{ - Configuration = (& $mockGetCimInstance_ConfigurationSetting_DefaultInstance)[0] - ReportsApplicationName = 'ReportManager' - SqlVersion = $TestCaseVersion + InstanceName = $mockDefaultInstanceName + CurrentVersion = "$TestCaseVersion.0.0.0" } } + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] "$TestCaseVersion.0.0.0" + } + Mock -CommandName Get-CimInstance ` -MockWith $mockGetCimInstance_Language ` -ParameterFilter $mockGetCimInstance_OperatingSystem_ParameterFilter @@ -1038,36 +1118,49 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { Context "When configuring a named instance of SQL Server Reporting Services 2019 that have been initialized by restarting the service" { BeforeAll { - $script:alreadyCalledGetReportingServicesData = $false + $script:alreadyCalledGetSqlDscRSConfiguration = $false $script:mockDynamicIsInitialized = $false $script:mockDynamicSecureConnectionLevel = $false - Mock -CommandName Get-ReportingServicesData -MockWith { - if ($script:alreadyCalledGetReportingServicesData) + Mock -CommandName Get-SqlDscRSConfiguration -MockWith { + if ($script:alreadyCalledGetSqlDscRSConfiguration) { $script:mockDynamicIsInitialized = $true } else { - $script:alreadyCalledGetReportingServicesData = $true + $script:alreadyCalledGetSqlDscRSConfiguration = $true } + return ( + New-Object -TypeName Microsoft.Management.Infrastructure.CimInstance -ArgumentList @( + 'MSReportServer_ConfigurationSetting' + 'root/Microsoft/SQLServer/ReportServer/RS_SQL2019/v15/Admin' + ) | Add-Member -MemberType NoteProperty -Name 'DatabaseServerName' -Value "$mockReportingServicesDatabaseServerName\$mockReportingServicesDatabaseNamedInstanceName" -PassThru | + Add-Member -MemberType NoteProperty -Name 'IsInitialized' -Value $script:mockDynamicIsInitialized -PassThru | + Add-Member -MemberType NoteProperty -Name 'InstanceName' -Value $mockNamedInstanceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'VirtualDirectoryReportServer' -Value $mockVirtualDirectoryReportServerName -PassThru | + Add-Member -MemberType NoteProperty -Name 'VirtualDirectoryReportManager' -Value $mockVirtualDirectoryReportManagerName -PassThru | + Add-Member -MemberType NoteProperty -Name 'SecureConnectionLevel' -Value $script:mockDynamicSecureConnectionLevel -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'ServiceName' -Value $mockReportingServicesServiceName -PassThru -Force + ) + } + + Mock -CommandName Get-SqlDscRSWebPortalApplicationName -MockWith { + return 'ReportServerWebApp' + } + + Mock -CommandName Get-SqlDscRSSetupConfiguration -MockWith { return @{ - Configuration = New-Object -TypeName Microsoft.Management.Infrastructure.CimInstance -ArgumentList @( - 'MSReportServer_ConfigurationSetting' - 'root/Microsoft/SQLServer/ReportServer/RS_SQL2019/v15/Admin' - ) | Add-Member -MemberType NoteProperty -Name 'DatabaseServerName' -Value "$mockReportingServicesDatabaseServerName\$mockReportingServicesDatabaseNamedInstanceName" -PassThru | - Add-Member -MemberType NoteProperty -Name 'IsInitialized' -Value $script:mockDynamicIsInitialized -PassThru | - Add-Member -MemberType NoteProperty -Name 'InstanceName' -Value $mockNamedInstanceName -PassThru | - Add-Member -MemberType NoteProperty -Name 'VirtualDirectoryReportServer' -Value $mockVirtualDirectoryReportServerName -PassThru | - Add-Member -MemberType NoteProperty -Name 'VirtualDirectoryReportManager' -Value $mockVirtualDirectoryReportManagerName -PassThru | - Add-Member -MemberType NoteProperty -Name 'SecureConnectionLevel' -Value $script:mockDynamicSecureConnectionLevel -PassThru -Force | - Add-Member -MemberType NoteProperty -Name 'ServiceName' -Value $mockReportingServicesServiceName -PassThru -Force - ReportsApplicationName = 'ReportServerWebApp' - SqlVersion = $sqlVersion.Version + InstanceName = $mockNamedInstanceName + CurrentVersion = "$($sqlVersion.Version).0.0.0" } } + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] "$($sqlVersion.Version).0.0.0" + } + Mock -CommandName Test-TargetResource -MockWith { return $true } @@ -1526,254 +1619,3 @@ Describe 'SqlRS\Invoke-RsCimMethod' -Tag 'Helper' { } } } - -Describe 'SqlRS\Get-ReportingServicesData' -Tag 'Helper' { - BeforeAll { - $mockInstanceId = 'MSRS13.INSTANCE' # cSpell: disable-line - - $mockGetItemProperty_Sql2014 = { - return @{ - Version = '12.0.6024.0' - } - } - - $mockGetItemProperty_Sql2016 = { - return @{ - Version ='13.0.4001.0' - } - } - - $mockGetItemProperty_Sql2017 = { - return @{ - CurrentVersion = '14.0.6514.11481' - } - } - - $mockGetItemProperty_Sql2019 = { - return @{ - CurrentVersion = '15.0.2000.5' - } - } - - $mockGetItemProperty_InstanceNames_ParameterFilter = { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\RS' - } - - $mockGetItemProperty_Sql2014AndSql2016_ParameterFilter = { - $Path -eq ('HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\{0}\Setup' -f $mockInstanceId) - } - - $mockGetItemProperty_Sql2017AndSql2019_ParameterFilter = { - $Path -eq ('HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\{0}\MSSQLServer\CurrentVersion' -f $mockInstanceId) - } - - # Inject a stub in the module scope to support testing cross-plattform - InModuleScope -ScriptBlock { - function script:Get-CimInstance - { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-ParameterBlockParameterAttribute', '', Justification='The stub cannot use [Parameter()].')] - param - ( - $ClassName - ) - - return - } - } - } - - Context 'When there is a Reporting Services instance' { - BeforeAll { - Mock -CommandName Get-ItemProperty -MockWith { - return @{ - 'INSTANCE' = $mockInstanceId - } - } -ParameterFilter $mockGetItemProperty_InstanceNames_ParameterFilter - - Mock -CommandName Get-CimInstance -MockWith { - return @( - ( - New-Object -TypeName Microsoft.Management.Infrastructure.CimInstance -ArgumentList @( - 'MSReportServer_ConfigurationSetting' - 'root/Microsoft/SQLServer/ReportServer/RS_SQL2016/v13/Admin' - ) | Add-Member -MemberType NoteProperty -Name 'DatabaseServerName' -Value 'MockDatabaseServer\MockDatabaseInstance' -PassThru | - Add-Member -MemberType NoteProperty -Name 'IsInitialized' -Value $false -PassThru | - Add-Member -MemberType NoteProperty -Name 'InstanceName' -Value 'INSTANCE' -PassThru | - Add-Member -MemberType NoteProperty -Name 'VirtualDirectoryReportServer' -Value 'ReportServer' -PassThru | - Add-Member -MemberType NoteProperty -Name 'VirtualDirectoryReportManager' -Value 'Reports' -PassThru | - Add-Member -MemberType NoteProperty -Name 'SecureConnectionLevel' -Value $false -PassThru -Force - ), - ( - # Array is a regression test for issue #819. - New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name 'DatabaseServerName' -Value 'MockDatabaseServer\MockDatabaseInstance' -PassThru | - Add-Member -MemberType NoteProperty -Name 'IsInitialized' -Value $true -PassThru | - Add-Member -MemberType NoteProperty -Name 'InstanceName' -Value 'DummyInstance' -PassThru -Force - ) - ) - } -ParameterFilter { - $ClassName -eq 'MSReportServer_ConfigurationSetting' - } - } - - Context 'When the instance is SQL Server Reporting Services 2014 or older' { - BeforeAll { - Mock -CommandName Test-Path -MockWith { - return $false - } - - Mock -CommandName Get-ItemProperty ` - -MockWith $mockGetItemProperty_Sql2014 ` - -ParameterFilter $mockGetItemProperty_Sql2014AndSql2016_ParameterFilter - } - - It 'Should return the correct information' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $getReportingServicesDataResult = Get-ReportingServicesData -InstanceName 'INSTANCE' - - $getReportingServicesDataResult.Configuration | Should -BeOfType [Microsoft.Management.Infrastructure.CimInstance] - $getReportingServicesDataResult.Configuration.InstanceName | Should -Be 'INSTANCE' - $getReportingServicesDataResult.Configuration.DatabaseServerName | Should -Be 'MockDatabaseServer\MockDatabaseInstance' - $getReportingServicesDataResult.Configuration.IsInitialized | Should -BeFalse - $getReportingServicesDataResult.Configuration.VirtualDirectoryReportServer | Should -Be 'ReportServer' - $getReportingServicesDataResult.Configuration.VirtualDirectoryReportManager | Should -Be 'Reports' - $getReportingServicesDataResult.Configuration.SecureConnectionLevel | Should -Be 0 - $getReportingServicesDataResult.ReportsApplicationName | Should -Be 'ReportManager' - $getReportingServicesDataResult.SqlVersion | Should -Be '12' - } - - Should -Invoke -CommandName Get-ItemProperty ` - -ParameterFilter $mockGetItemProperty_InstanceNames_ParameterFilter ` - -Exactly -Times 2 -Scope 'It' - - Should -Invoke -CommandName Get-ItemProperty ` - -ParameterFilter $mockGetItemProperty_Sql2014AndSql2016_ParameterFilter ` - -Exactly -Times 1 -Scope 'It' - - Should -Invoke -CommandName Get-CimInstance -Exactly -Times 1 -Scope 'It' - } - } - - Context 'When the instance is SQL Server Reporting Services 2016' { - BeforeAll { - Mock -CommandName Test-Path -MockWith { - return $false - } - - Mock -CommandName Get-ItemProperty ` - -MockWith $mockGetItemProperty_Sql2016 ` - -ParameterFilter $mockGetItemProperty_Sql2014AndSql2016_ParameterFilter - } - - It 'Should return the correct information' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $getReportingServicesDataResult = Get-ReportingServicesData -InstanceName 'INSTANCE' - - $getReportingServicesDataResult.Configuration | Should -BeOfType [Microsoft.Management.Infrastructure.CimInstance] - $getReportingServicesDataResult.Configuration.InstanceName | Should -Be 'INSTANCE' - $getReportingServicesDataResult.Configuration.DatabaseServerName | Should -Be 'MockDatabaseServer\MockDatabaseInstance' - $getReportingServicesDataResult.Configuration.IsInitialized | Should -BeFalse - $getReportingServicesDataResult.Configuration.VirtualDirectoryReportServer | Should -Be 'ReportServer' - $getReportingServicesDataResult.Configuration.VirtualDirectoryReportManager | Should -Be 'Reports' - $getReportingServicesDataResult.Configuration.SecureConnectionLevel | Should -Be 0 - $getReportingServicesDataResult.ReportsApplicationName | Should -Be 'ReportServerWebApp' - $getReportingServicesDataResult.SqlVersion | Should -Be '13' - } - - Should -Invoke -CommandName Get-ItemProperty ` - -ParameterFilter $mockGetItemProperty_InstanceNames_ParameterFilter ` - -Exactly -Times 2 -Scope 'It' - - Should -Invoke -CommandName Get-ItemProperty ` - -ParameterFilter $mockGetItemProperty_Sql2014AndSql2016_ParameterFilter ` - -Exactly -Times 1 -Scope 'It' - - Should -Invoke -CommandName Get-CimInstance -Exactly -Times 1 -Scope 'It' - } - } - - Context 'When the instance is SQL Server Reporting Services 2017' { - BeforeAll { - Mock -CommandName Test-Path -MockWith { - return $true - } - - Mock -CommandName Get-ItemProperty ` - -MockWith $mockGetItemProperty_Sql2017 ` - -ParameterFilter $mockGetItemProperty_Sql2017AndSql2019_ParameterFilter - } - - It 'Should return the correct information' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $getReportingServicesDataResult = Get-ReportingServicesData -InstanceName 'INSTANCE' - - $getReportingServicesDataResult.Configuration | Should -BeOfType [Microsoft.Management.Infrastructure.CimInstance] - $getReportingServicesDataResult.Configuration.InstanceName | Should -Be 'INSTANCE' - $getReportingServicesDataResult.Configuration.DatabaseServerName | Should -Be 'MockDatabaseServer\MockDatabaseInstance' - $getReportingServicesDataResult.Configuration.IsInitialized | Should -BeFalse - $getReportingServicesDataResult.Configuration.VirtualDirectoryReportServer | Should -Be 'ReportServer' - $getReportingServicesDataResult.Configuration.VirtualDirectoryReportManager | Should -Be 'Reports' - $getReportingServicesDataResult.Configuration.SecureConnectionLevel | Should -Be 0 - $getReportingServicesDataResult.ReportsApplicationName | Should -Be 'ReportServerWebApp' - $getReportingServicesDataResult.SqlVersion | Should -Be '14' - } - - Should -Invoke -CommandName Get-ItemProperty ` - -ParameterFilter $mockGetItemProperty_InstanceNames_ParameterFilter ` - -Exactly -Times 2 -Scope 'It' - - Should -Invoke -CommandName Get-ItemProperty ` - -ParameterFilter $mockGetItemProperty_Sql2017AndSql2019_ParameterFilter ` - -Exactly -Times 1 -Scope 'It' - - Should -Invoke -CommandName Get-CimInstance -Exactly -Times 1 -Scope 'It' - } - } - - Context 'When the instance is SQL Server Reporting Services 2019' { - BeforeAll { - Mock -CommandName Test-Path -MockWith { - return $true - } - - Mock -CommandName Get-ItemProperty ` - -MockWith $mockGetItemProperty_Sql2019 ` - -ParameterFilter $mockGetItemProperty_Sql2017AndSql2019_ParameterFilter - } - - It 'Should return the correct information' { - InModuleScope -ScriptBlock { - Set-StrictMode -Version 1.0 - - $getReportingServicesDataResult = Get-ReportingServicesData -InstanceName 'INSTANCE' - - $getReportingServicesDataResult.Configuration | Should -BeOfType [Microsoft.Management.Infrastructure.CimInstance] - $getReportingServicesDataResult.Configuration.InstanceName | Should -Be 'INSTANCE' - $getReportingServicesDataResult.Configuration.DatabaseServerName | Should -Be 'MockDatabaseServer\MockDatabaseInstance' - $getReportingServicesDataResult.Configuration.IsInitialized | Should -BeFalse - $getReportingServicesDataResult.Configuration.VirtualDirectoryReportServer | Should -Be 'ReportServer' - $getReportingServicesDataResult.Configuration.VirtualDirectoryReportManager | Should -Be 'Reports' - $getReportingServicesDataResult.Configuration.SecureConnectionLevel | Should -Be 0 - $getReportingServicesDataResult.ReportsApplicationName | Should -Be 'ReportServerWebApp' - $getReportingServicesDataResult.SqlVersion | Should -Be '15' - } - - Should -Invoke -CommandName Get-ItemProperty ` - -ParameterFilter $mockGetItemProperty_InstanceNames_ParameterFilter ` - -Exactly -Times 2 -Scope 'It' - - Should -Invoke -CommandName Get-ItemProperty ` - -ParameterFilter $mockGetItemProperty_Sql2017AndSql2019_ParameterFilter ` - -Exactly -Times 1 -Scope 'It' - - Should -Invoke -CommandName Get-CimInstance -Exactly -Times 1 -Scope 'It' - } - } - } -} diff --git a/tests/Unit/Public/Get-SqlDscRSVersion.Tests.ps1 b/tests/Unit/Public/Get-SqlDscRSVersion.Tests.ps1 new file mode 100644 index 0000000000..46f766cec6 --- /dev/null +++ b/tests/Unit/Public/Get-SqlDscRSVersion.Tests.ps1 @@ -0,0 +1,165 @@ +[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-SqlDscRSVersion' { + Context 'When validating parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-Configuration] []' + } + ) { + $result = (Get-Command -Name 'Get-SqlDscRSVersion').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 the version successfully' { + It 'Should return the version for SQL Server 2016 (version 13)' { + $mockConfiguration = @{ + CurrentVersion = '13.0.4001.0' + } + + $result = $mockConfiguration | Get-SqlDscRSVersion + + $result | Should -BeOfType [System.Version] + $result.Major | Should -Be 13 + $result.Minor | Should -Be 0 + $result.Build | Should -Be 4001 + $result.Revision | Should -Be 0 + } + + It 'Should return the version for SQL Server 2017 (version 14)' { + $mockConfiguration = @{ + CurrentVersion = '14.0.600.250' + } + + $result = $mockConfiguration | Get-SqlDscRSVersion + + $result | Should -BeOfType [System.Version] + $result.Major | Should -Be 14 + $result.Minor | Should -Be 0 + $result.Build | Should -Be 600 + $result.Revision | Should -Be 250 + } + + It 'Should return the version for SQL Server 2019 (version 15)' { + $mockConfiguration = @{ + CurrentVersion = '15.0.1100.0' + } + + $result = $mockConfiguration | Get-SqlDscRSVersion + + $result | Should -BeOfType [System.Version] + $result.Major | Should -Be 15 + } + + It 'Should return the version for SQL Server 2022 (version 16)' { + $mockConfiguration = @{ + CurrentVersion = '16.0.1000.6' + } + + $result = $mockConfiguration | Get-SqlDscRSVersion + + $result | Should -BeOfType [System.Version] + $result.Major | Should -Be 16 + } + + It 'Should return the version for SQL Server 2014 (version 12)' { + $mockConfiguration = @{ + CurrentVersion = '12.0.4100.0' + } + + $result = $mockConfiguration | Get-SqlDscRSVersion + + $result | Should -BeOfType [System.Version] + $result.Major | Should -Be 12 + } + } + + Context 'When CurrentVersion is null or empty' { + It 'Should write an error when CurrentVersion is null' { + $mockConfiguration = @{ + CurrentVersion = $null + } + + $mockConfiguration | Get-SqlDscRSVersion -ErrorVariable mockErrorVariable -ErrorAction 'SilentlyContinue' + + $mockErrorVariable | Should -HaveCount 1 + $mockErrorVariable[0].FullyQualifiedErrorId | Should -Be 'GSRSV0001,Get-SqlDscRSVersion' + } + + It 'Should write an error when CurrentVersion is empty' { + $mockConfiguration = @{ + CurrentVersion = '' + } + + $mockConfiguration | Get-SqlDscRSVersion -ErrorVariable mockErrorVariable -ErrorAction 'SilentlyContinue' + + $mockErrorVariable | Should -HaveCount 1 + $mockErrorVariable[0].FullyQualifiedErrorId | Should -Be 'GSRSV0001,Get-SqlDscRSVersion' + } + } + + Context 'When using parameter instead of pipeline input' { + It 'Should return the version when Configuration is passed as parameter' { + $mockConfiguration = @{ + CurrentVersion = '15.0.1100.0' + } + + $result = Get-SqlDscRSVersion -Configuration $mockConfiguration + + $result | Should -BeOfType [System.Version] + $result.Major | Should -Be 15 + } + } +} diff --git a/tests/Unit/Public/Get-SqlDscRSWebPortalApplicationName.Tests.ps1 b/tests/Unit/Public/Get-SqlDscRSWebPortalApplicationName.Tests.ps1 new file mode 100644 index 0000000000..821bbde7ef --- /dev/null +++ b/tests/Unit/Public/Get-SqlDscRSWebPortalApplicationName.Tests.ps1 @@ -0,0 +1,195 @@ +[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-SqlDscRSWebPortalApplicationName' { + Context 'When validating parameter sets' { + It 'Should have the correct parameters in parameter set ' -ForEach @( + @{ + ExpectedParameterSetName = '__AllParameterSets' + ExpectedParameters = '[-Configuration] []' + } + ) { + $result = (Get-Command -Name 'Get-SqlDscRSWebPortalApplicationName').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 SQL Server version is 2016 (version 13) or later' { + BeforeAll { + Mock -CommandName Get-SqlDscRSVersion + } + + It 'Should return ReportServerWebApp for SQL Server 2016 (version 13)' { + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] '13.0.4001.0' + } + + $mockConfiguration = @{ + InstanceName = 'SSRS' + } + + $result = $mockConfiguration | Get-SqlDscRSWebPortalApplicationName + + $result | Should -Be 'ReportServerWebApp' + } + + It 'Should return ReportServerWebApp for SQL Server 2017 (version 14)' { + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] '14.0.600.0' + } + + $mockConfiguration = @{ + InstanceName = 'SSRS' + } + + $result = $mockConfiguration | Get-SqlDscRSWebPortalApplicationName + + $result | Should -Be 'ReportServerWebApp' + } + + It 'Should return ReportServerWebApp for SQL Server 2019 (version 15)' { + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] '15.0.1100.0' + } + + $mockConfiguration = @{ + InstanceName = 'SSRS' + } + + $result = $mockConfiguration | Get-SqlDscRSWebPortalApplicationName + + $result | Should -Be 'ReportServerWebApp' + } + + It 'Should return ReportServerWebApp for SQL Server 2022 (version 16)' { + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] '16.0.1000.0' + } + + $mockConfiguration = @{ + InstanceName = 'SSRS' + } + + $result = $mockConfiguration | Get-SqlDscRSWebPortalApplicationName + + $result | Should -Be 'ReportServerWebApp' + } + } + + Context 'When SQL Server version is earlier than 2016 (version 12 and below)' { + BeforeAll { + Mock -CommandName Get-SqlDscRSVersion + } + + It 'Should return ReportManager for SQL Server 2014 (version 12)' { + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] '12.0.4100.0' + } + + $mockConfiguration = @{ + InstanceName = 'SSRS' + } + + $result = $mockConfiguration | Get-SqlDscRSWebPortalApplicationName + + $result | Should -Be 'ReportManager' + } + + It 'Should return ReportManager for SQL Server 2012 (version 11)' { + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] '11.0.5000.0' + } + + $mockConfiguration = @{ + InstanceName = 'SSRS' + } + + $result = $mockConfiguration | Get-SqlDscRSWebPortalApplicationName + + $result | Should -Be 'ReportManager' + } + } + + Context 'When Get-SqlDscRSVersion returns null' { + BeforeAll { + Mock -CommandName Get-SqlDscRSVersion + } + + It 'Should return null when Get-SqlDscRSVersion returns null' { + $mockConfiguration = @{ + InstanceName = 'SSRS' + } + + $result = $mockConfiguration | Get-SqlDscRSWebPortalApplicationName + + $result | Should -BeNullOrEmpty + } + } + + Context 'When using parameter instead of pipeline input' { + BeforeAll { + Mock -CommandName Get-SqlDscRSVersion -MockWith { + return [System.Version] '15.0.1100.0' + } + } + + It 'Should return the correct application name when Configuration is passed as parameter' { + $mockConfiguration = @{ + InstanceName = 'SSRS' + } + + $result = Get-SqlDscRSWebPortalApplicationName -Configuration $mockConfiguration + + $result | Should -Be 'ReportServerWebApp' + } + } +} diff --git a/tests/Unit/Public/Set-SqlDscRSVirtualDirectory.Tests.ps1 b/tests/Unit/Public/Set-SqlDscRSVirtualDirectory.Tests.ps1 new file mode 100644 index 0000000000..7bb876a2d9 --- /dev/null +++ b/tests/Unit/Public/Set-SqlDscRSVirtualDirectory.Tests.ps1 @@ -0,0 +1,249 @@ +[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-SqlDscRSVirtualDirectory' { + 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] [-VirtualDirectory] [[-Lcid] ] [-PassThru] [-Force] [-WhatIf] [-Confirm] []' + } + ) { + $result = (Get-Command -Name 'Set-SqlDscRSVirtualDirectory').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 virtual directory successfully' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should set virtual directory without errors' { + { $mockCimInstance | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $MethodName -eq 'SetVirtualDirectory' -and + $Arguments.Application -eq 'ReportServerWebService' -and + $Arguments.VirtualDirectory -eq 'ReportServer' -and + $Arguments.Lcid -eq 1033 + } -Exactly -Times 1 + } + + It 'Should not return anything by default' { + $result = $mockCimInstance | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Confirm:$false + + $result | Should -BeNullOrEmpty + } + } + + Context 'When setting virtual directory with PassThru' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should return the configuration CIM instance' { + $result = $mockCimInstance | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -PassThru -Confirm:$false + + $result | Should -Not -BeNullOrEmpty + $result.InstanceName | Should -Be 'SSRS' + } + } + + Context 'When setting virtual directory with Force' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should set virtual directory without confirmation' { + { $mockCimInstance | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Force } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -Exactly -Times 1 + } + } + + Context 'When setting virtual directory with custom Lcid' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should use the specified Lcid' { + { $mockCimInstance | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Lcid 1031 -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $Arguments.Lcid -eq 1031 + } -Exactly -Times 1 + } + + It 'Should not call Get-OperatingSystem when Lcid is specified' { + $mockCimInstance | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Lcid 1031 -Confirm:$false + + Should -Invoke -CommandName Get-OperatingSystem -Exactly -Times 0 + } + } + + Context 'When CIM method fails' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod -MockWith { + throw 'Method SetVirtualDirectory() failed with an error. Error: Access denied (HRESULT:-2147024891)' + } + } + + It 'Should throw a terminating error' { + { $mockCimInstance | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -Confirm:$false } | Should -Throw -ErrorId 'SSRSVD0001,Set-SqlDscRSVirtualDirectory' + } + } + + Context 'When using WhatIf' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should not call Invoke-RsCimMethod' { + $mockCimInstance | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -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 set virtual directory' { + { Set-SqlDscRSVirtualDirectory -Configuration $mockCimInstance -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer' -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 | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebApp' -VirtualDirectory 'Reports' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $Arguments.Application -eq 'ReportServerWebApp' + } -Exactly -Times 1 + } + + It 'Should accept ReportManager application' { + { $mockCimInstance | Set-SqlDscRSVirtualDirectory -Application 'ReportManager' -VirtualDirectory 'Reports' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $Arguments.Application -eq 'ReportManager' + } -Exactly -Times 1 + } + } + + Context 'When using different virtual directory names' { + BeforeAll { + $mockCimInstance = [PSCustomObject] @{ + InstanceName = 'SSRS' + } + + Mock -CommandName Invoke-RsCimMethod + } + + It 'Should accept custom virtual directory name' { + { $mockCimInstance | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory 'ReportServer_Custom' -Confirm:$false } | Should -Not -Throw + + Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { + $Arguments.VirtualDirectory -eq 'ReportServer_Custom' + } -Exactly -Times 1 + } + } +} From 290c1f6e28789c2c2d2a274f6210d640da539226 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 14:13:34 +0100 Subject: [PATCH 06/14] Refactor Set-TargetResource to use Set-SqlDscRSVirtualDirectory for managing virtual directories --- CHANGELOG.md | 3 ++ source/DSCResources/DSC_SqlRS/DSC_SqlRS.psm1 | 48 ++------------------ 2 files changed, 7 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87c956f098..98580ad42c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -153,6 +153,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `Add-SqlDscRSUrlReservation`, `Remove-SqlDscRSUrlReservation`, and `Set-SqlDscRSUrlReservation` for managing URL reservations instead of calling the CIM methods directly. + - Refactored to use the public command `Set-SqlDscRSVirtualDirectory` for + setting virtual directories instead of calling the CIM method directly + ([issue #2015](https://github.com/dsccommunity/SqlServerDsc/issues/2015)). - `Assert-SetupActionProperties` - Refactored to use the command `Get-FileVersion` from the DscResource.Common module instead of the private function `Get-FileVersionInformation` diff --git a/source/DSCResources/DSC_SqlRS/DSC_SqlRS.psm1 b/source/DSCResources/DSC_SqlRS/DSC_SqlRS.psm1 index 411e0cb426..bbf82aa16f 100644 --- a/source/DSCResources/DSC_SqlRS/DSC_SqlRS.psm1 +++ b/source/DSCResources/DSC_SqlRS/DSC_SqlRS.psm1 @@ -372,17 +372,7 @@ function Set-TargetResource Write-Verbose -Message "Setting report server virtual directory on $DatabaseServerName\$DatabaseInstanceName to '$ReportServerVirtualDirectory'." # cSpell: ignore Lcid - $invokeRsCimMethodParameters = @{ - CimInstance = $rsConfiguration - MethodName = 'SetVirtualDirectory' - Arguments = @{ - Application = 'ReportServerWebService' - VirtualDirectory = $ReportServerVirtualDirectory - Lcid = $language - } - } - - Invoke-RsCimMethod @invokeRsCimMethodParameters + $rsConfiguration | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory $ReportServerVirtualDirectory -Lcid $language -Force -ErrorAction 'Stop' $ReportServerReservedUrl | ForEach-Object -Process { Write-Verbose -Message "Adding report server URL reservation on $DatabaseServerName\$DatabaseInstanceName`: $_." @@ -395,17 +385,7 @@ function Set-TargetResource { Write-Verbose -Message "Setting reports virtual directory on $DatabaseServerName\$DatabaseInstanceName to '$ReportServerVirtualDirectory'." - $invokeRsCimMethodParameters = @{ - CimInstance = $rsConfiguration - MethodName = 'SetVirtualDirectory' - Arguments = @{ - Application = $reportsApplicationName - VirtualDirectory = $ReportsVirtualDirectory - Lcid = $language - } - } - - Invoke-RsCimMethod @invokeRsCimMethodParameters + $rsConfiguration | Set-SqlDscRSVirtualDirectory -Application $reportsApplicationName -VirtualDirectory $ReportsVirtualDirectory -Lcid $language -Force -ErrorAction 'Stop' $ReportsReservedUrl | ForEach-Object -Process { Write-Verbose -Message "Adding reports URL reservation on $DatabaseServerName\$DatabaseInstanceName`: $_." @@ -637,17 +617,7 @@ function Set-TargetResource $rsConfiguration | Remove-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $_ -Lcid $language -Force -ErrorAction 'Stop' } - $invokeRsCimMethodParameters = @{ - CimInstance = $rsConfiguration - MethodName = 'SetVirtualDirectory' - Arguments = @{ - Application = 'ReportServerWebService' - VirtualDirectory = $ReportServerVirtualDirectory - Lcid = $language - } - } - - Invoke-RsCimMethod @invokeRsCimMethodParameters -ErrorAction 'Stop' + $rsConfiguration | Set-SqlDscRSVirtualDirectory -Application 'ReportServerWebService' -VirtualDirectory $ReportServerVirtualDirectory -Lcid $language -Force -ErrorAction 'Stop' $currentConfig.ReportServerReservedUrl | ForEach-Object -Process { $rsConfiguration | Add-SqlDscRSUrlReservation -Application 'ReportServerWebService' -UrlString $_ -Lcid $language -Force -ErrorAction 'Stop' @@ -664,17 +634,7 @@ function Set-TargetResource $rsConfiguration | Remove-SqlDscRSUrlReservation -Application $reportsApplicationName -UrlString $_ -Lcid $language -Force -ErrorAction 'Stop' } - $invokeRsCimMethodParameters = @{ - CimInstance = $rsConfiguration - MethodName = 'SetVirtualDirectory' - Arguments = @{ - Application = $reportsApplicationName - VirtualDirectory = $ReportsVirtualDirectory - Lcid = $language - } - } - - Invoke-RsCimMethod @invokeRsCimMethodParameters -ErrorAction 'Stop' + $rsConfiguration | Set-SqlDscRSVirtualDirectory -Application $reportsApplicationName -VirtualDirectory $ReportsVirtualDirectory -Lcid $language -Force -ErrorAction 'Stop' $currentConfig.ReportsReservedUrl | ForEach-Object -Process { $rsConfiguration | Add-SqlDscRSUrlReservation -Application $reportsApplicationName -UrlString $_ -Lcid $language -Force -ErrorAction 'Stop' From 5e118335a954d25d09be86130a94dedb3f28dc0e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 14:32:30 +0100 Subject: [PATCH 07/14] Refactor Get-OperatingSystem function to improve variable naming for clarity --- source/Private/Get-OperatingSystem.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Private/Get-OperatingSystem.ps1 b/source/Private/Get-OperatingSystem.ps1 index 956601be7f..280fdfe594 100644 --- a/source/Private/Get-OperatingSystem.ps1 +++ b/source/Private/Get-OperatingSystem.ps1 @@ -34,14 +34,14 @@ function Get-OperatingSystem Write-Verbose -Message $script:localizedData.Get_OperatingSystem_Getting - $wmiOperatingSystem = Get-CimInstance -ClassName 'Win32_OperatingSystem' -Namespace 'root/cimv2' -ErrorAction 'SilentlyContinue' + $operatingSystem = Get-CimInstance -ClassName 'Win32_OperatingSystem' -Namespace 'root/cimv2' -ErrorAction 'SilentlyContinue' - if ($null -eq $wmiOperatingSystem) + if ($null -eq $operatingSystem) { Write-Error -Message $script:localizedData.Get_OperatingSystem_FailedToGet -Category 'ObjectNotFound' -ErrorId 'GOS0001' -TargetObject $null return } - return $wmiOperatingSystem + return $operatingSystem } From 9e9e91a75ec5bd05cd40c1a41d44d4b5d516d333 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 14:33:46 +0100 Subject: [PATCH 08/14] Remove unnecessary parameters from Remove-Item in AfterAll block of Set-SqlDscRSUrlReservation tests --- tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 b/tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 index ed25980aa4..3cfda6d055 100644 --- a/tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 +++ b/tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 @@ -36,7 +36,7 @@ BeforeAll { } AfterAll { - Remove-Item -Path 'env:SqlServerDscCI' -Force -ErrorAction 'SilentlyContinue' + Remove-Item -Path 'env:SqlServerDscCI' $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') $PSDefaultParameterValues.Remove('Mock:ModuleName') From 51e9aa1a140bf31f41f74336ae0250e453e33719 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 14:34:26 +0100 Subject: [PATCH 09/14] Update mock SQL version in Set-TargetResource tests to reflect current version --- tests/Unit/DSC_SqlRS.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/DSC_SqlRS.Tests.ps1 b/tests/Unit/DSC_SqlRS.Tests.ps1 index 0650caae38..caeefc1f13 100644 --- a/tests/Unit/DSC_SqlRS.Tests.ps1 +++ b/tests/Unit/DSC_SqlRS.Tests.ps1 @@ -1153,12 +1153,12 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { Mock -CommandName Get-SqlDscRSSetupConfiguration -MockWith { return @{ InstanceName = $mockNamedInstanceName - CurrentVersion = "$($sqlVersion.Version).0.0.0" + CurrentVersion = '15.0.0.0' } } Mock -CommandName Get-SqlDscRSVersion -MockWith { - return [System.Version] "$($sqlVersion.Version).0.0.0" + return [System.Version] '15.0.0.0' } Mock -CommandName Test-TargetResource -MockWith { From 5267f6cf39d6436dd4ac6ca20e246b9adff71987 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 14:34:34 +0100 Subject: [PATCH 10/14] Add error handling to Import-Module in BeforeAll block of Set-SqlDscRSUrlReservation tests --- tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 b/tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 index 3cfda6d055..a0b9a54ccc 100644 --- a/tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 +++ b/tests/Unit/Public/Set-SqlDscRSUrlReservation.Tests.ps1 @@ -28,7 +28,7 @@ BeforeAll { $env:SqlServerDscCI = $true - Import-Module -Name $script:moduleName + Import-Module -Name $script:moduleName -ErrorAction 'Stop' $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:moduleName $PSDefaultParameterValues['Mock:ModuleName'] = $script:moduleName From 51e431dd419a5b6db719b6a9196f828d509c4709 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 14:36:26 +0100 Subject: [PATCH 11/14] Add suppression message for syntactically correct examples in Get-SqlDscRSVersion and Get-SqlDscRSWebPortalApplicationName functions --- source/Public/Get-SqlDscRSVersion.ps1 | 1 + source/Public/Get-SqlDscRSWebPortalApplicationName.ps1 | 1 + 2 files changed, 2 insertions(+) diff --git a/source/Public/Get-SqlDscRSVersion.ps1 b/source/Public/Get-SqlDscRSVersion.ps1 index d0bfb1ca7e..1b01cea53f 100644 --- a/source/Public/Get-SqlDscRSVersion.ps1 +++ b/source/Public/Get-SqlDscRSVersion.ps1 @@ -31,6 +31,7 @@ #> function Get-SqlDscRSVersion { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] [CmdletBinding()] [OutputType([System.Version])] param diff --git a/source/Public/Get-SqlDscRSWebPortalApplicationName.ps1 b/source/Public/Get-SqlDscRSWebPortalApplicationName.ps1 index 18b1d50dde..15b542f53a 100644 --- a/source/Public/Get-SqlDscRSWebPortalApplicationName.ps1 +++ b/source/Public/Get-SqlDscRSWebPortalApplicationName.ps1 @@ -37,6 +37,7 @@ #> function Get-SqlDscRSWebPortalApplicationName { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the examples use pipeline input the rule cannot validate.')] [CmdletBinding()] [OutputType([System.String])] param From b2d31b5538905e9cdd97f81d3a23131ba16b3cfe Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 20:18:19 +0100 Subject: [PATCH 12/14] Reorder URL strings and update command invocations in SqlRS tests for consistency --- tests/Unit/DSC_SqlRS.Tests.ps1 | 91 ++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/tests/Unit/DSC_SqlRS.Tests.ps1 b/tests/Unit/DSC_SqlRS.Tests.ps1 index caeefc1f13..f357dfcfed 100644 --- a/tests/Unit/DSC_SqlRS.Tests.ps1 +++ b/tests/Unit/DSC_SqlRS.Tests.ps1 @@ -99,8 +99,8 @@ Describe 'SqlRS\Get-TargetResource' -Tag 'Get' { } -PassThru | Add-Member -MemberType ScriptProperty -Name 'UrlString' -Value { return @( - $mockDynamicReportsApplicationUrlString, - $mockDynamicReportServerApplicationUrlString + $mockDynamicReportServerApplicationUrlString, + $mockDynamicReportsApplicationUrlString ) } -PassThru -Force } @@ -449,8 +449,8 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { } -PassThru | Add-Member -MemberType ScriptProperty -Name 'UrlString' -Value { return @( - $mockDynamicReportsApplicationUrlString, - $mockDynamicReportServerApplicationUrlString + $mockDynamicReportServerApplicationUrlString, + $mockDynamicReportsApplicationUrlString ) } -PassThru -Force } @@ -534,6 +534,9 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { Mock -CommandName Disable-SqlDscRsSecureConnection Mock -CommandName Restart-ReportingServicesService Mock -CommandName Start-Sleep + Mock -CommandName Set-SqlDscRSVirtualDirectory + Mock -CommandName Add-SqlDscRSUrlReservation + Mock -CommandName Set-SqlDscRSUrlReservation Mock -CommandName Invoke-RsCimMethod Mock -CommandName Invoke-RsCimMethod -MockWith $mockInvokeRsCimMethod_GenerateDatabaseCreationScript -ParameterFilter { $MethodName -eq 'GenerateDatabaseCreationScript' @@ -645,20 +648,20 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { $MethodName -eq 'GenerateDatabaseCreationScript' } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'SetVirtualDirectory' -and $Arguments.Application -eq $mockReportServerApplicationName + Should -Invoke -CommandName Set-SqlDscRSVirtualDirectory -ParameterFilter { + $Application -eq $mockReportServerApplicationName } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'SetVirtualDirectory' -and $Arguments.Application -eq $mockReportsApplicationName + Should -Invoke -CommandName Set-SqlDscRSVirtualDirectory -ParameterFilter { + $Application -eq $mockReportsApplicationName } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'ReserveUrl' -and $Arguments.Application -eq $mockReportServerApplicationName + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportServerApplicationName } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'ReserveUrl' -and $Arguments.Application -eq $mockReportsApplicationName + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportsApplicationName } -Exactly -Times 1 -Scope It Should -Invoke -CommandName Get-CimInstance -Exactly -Times 1 -Scope It @@ -897,20 +900,20 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { $MethodName -eq 'GenerateDatabaseCreationScript' } -Exactly -Times 0 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'SetVirtualDirectory' -and $Arguments.Application -eq $mockReportServerApplicationName + Should -Invoke -CommandName Set-SqlDscRSVirtualDirectory -ParameterFilter { + $Application -eq $mockReportServerApplicationName } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'SetVirtualDirectory' -and $Arguments.Application -eq $mockReportsApplicationName + Should -Invoke -CommandName Set-SqlDscRSVirtualDirectory -ParameterFilter { + $Application -eq $mockReportsApplicationName } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'ReserveUrl' -and $Arguments.Application -eq $mockReportServerApplicationName + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportServerApplicationName } -Exactly -Times 2 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'ReserveUrl' -and $Arguments.Application -eq $mockReportsApplicationName + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportsApplicationName } -Exactly -Times 2 -Scope It Should -Invoke -CommandName Get-CimInstance -Exactly -Times 1 -Scope It @@ -1001,20 +1004,20 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { $MethodName -eq 'GenerateDatabaseCreationScript' } -Exactly -Times 0 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'SetVirtualDirectory' -and $Arguments.Application -eq $mockReportServerApplicationName + Should -Invoke -CommandName Set-SqlDscRSVirtualDirectory -ParameterFilter { + $Application -eq $mockReportServerApplicationName } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'SetVirtualDirectory' -and $Arguments.Application -eq $mockReportsApplicationName + Should -Invoke -CommandName Set-SqlDscRSVirtualDirectory -ParameterFilter { + $Application -eq $mockReportsApplicationName } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'ReserveUrl' -and $Arguments.Application -eq $mockReportServerApplicationName + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportServerApplicationName } -Exactly -Times 2 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'ReserveUrl' -and $Arguments.Application -eq $mockReportsApplicationName + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportsApplicationName } -Exactly -Times 2 -Scope It Should -Invoke -CommandName Get-CimInstance -Exactly -Times 1 -Scope It @@ -1089,20 +1092,20 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { $MethodName -eq 'GenerateDatabaseCreationScript' } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'SetVirtualDirectory' -and $Arguments.Application -eq $mockReportServerApplicationName + Should -Invoke -CommandName Set-SqlDscRSVirtualDirectory -ParameterFilter { + $Application -eq $mockReportServerApplicationName } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'SetVirtualDirectory' -and $Arguments.Application -eq $mockReportsApplicationNameLegacy + Should -Invoke -CommandName Set-SqlDscRSVirtualDirectory -ParameterFilter { + $Application -eq $mockReportsApplicationNameLegacy } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'ReserveUrl' -and $Arguments.Application -eq $mockReportServerApplicationName + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportServerApplicationName } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'ReserveUrl' -and $Arguments.Application -eq $mockReportsApplicationNameLegacy + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportsApplicationNameLegacy } -Exactly -Times 1 -Scope It Should -Invoke -CommandName Get-CimInstance -Exactly -Times 1 -Scope It @@ -1205,20 +1208,20 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { $MethodName -eq 'GenerateDatabaseCreationScript' } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'SetVirtualDirectory' -and $Arguments.Application -eq $mockReportServerApplicationName + Should -Invoke -CommandName Set-SqlDscRSVirtualDirectory -ParameterFilter { + $Application -eq $mockReportServerApplicationName } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'SetVirtualDirectory' -and $Arguments.Application -eq $mockReportsApplicationName + Should -Invoke -CommandName Set-SqlDscRSVirtualDirectory -ParameterFilter { + $Application -eq $mockReportsApplicationName } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'ReserveUrl' -and $Arguments.Application -eq $mockReportServerApplicationName + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportServerApplicationName } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'ReserveUrl' -and $Arguments.Application -eq $mockReportsApplicationName + Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportsApplicationName } -Exactly -Times 1 -Scope It Should -Invoke -CommandName Get-CimInstance -Exactly -Times 1 -Scope It From 6ecb7a0c187a34aecb28a080c52ab47791711857 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 2 Jan 2026 21:42:27 +0100 Subject: [PATCH 13/14] Replace Invoke-RsCimMethod with Get-SqlDscRSUrlReservation in Get-TargetResource and Set-TargetResource tests for improved clarity --- tests/Unit/DSC_SqlRS.Tests.ps1 | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tests/Unit/DSC_SqlRS.Tests.ps1 b/tests/Unit/DSC_SqlRS.Tests.ps1 index f357dfcfed..a56803ad66 100644 --- a/tests/Unit/DSC_SqlRS.Tests.ps1 +++ b/tests/Unit/DSC_SqlRS.Tests.ps1 @@ -142,9 +142,7 @@ Describe 'SqlRS\Get-TargetResource' -Tag 'Get' { Add-Member -MemberType NoteProperty -Name 'ServiceName' -Value $mockReportingServicesServiceName -PassThru -Force } - Mock -CommandName Invoke-RsCimMethod -MockWith $mockInvokeRsCimMethod_ListReservedUrls -ParameterFilter { - $MethodName -eq 'ListReservedUrls' - } + Mock -CommandName Get-SqlDscRSUrlReservation -MockWith $mockInvokeRsCimMethod_ListReservedUrls InModuleScope -ScriptBlock { $script:mockNamedInstanceName = 'INSTANCE' @@ -223,9 +221,7 @@ Describe 'SqlRS\Get-TargetResource' -Tag 'Get' { $resultGetTargetResource.UseSsl | Should -BeFalse } - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'ListReservedUrls' - } -Exactly -Times 1 -Scope It + Should -Invoke -CommandName Get-SqlDscRSUrlReservation -Exactly -Times 1 -Scope It } Context 'When SSL is not used' { @@ -317,9 +313,7 @@ Describe 'SqlRS\Get-TargetResource' -Tag 'Get' { $resultGetTargetResource.ReportsReservedUrl | Should -BeNullOrEmpty } - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'ListReservedUrls' - } -Exactly -Times 0 -Scope It + Should -Invoke -CommandName Get-SqlDscRSUrlReservation -Exactly -Times 0 -Scope It } # Regression test for issue #822. @@ -518,9 +512,7 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { $ClassName -eq 'Win32_OperatingSystem' } - Mock -CommandName Invoke-RsCimMethod -MockWith $mockInvokeRsCimMethod_ListReservedUrls -ParameterFilter { - $MethodName -eq 'ListReservedUrls' - } + Mock -CommandName Get-SqlDscRSUrlReservation -MockWith $mockInvokeRsCimMethod_ListReservedUrls <# This is mocked here so that no calls are made to it directly, @@ -536,6 +528,7 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { Mock -CommandName Start-Sleep Mock -CommandName Set-SqlDscRSVirtualDirectory Mock -CommandName Add-SqlDscRSUrlReservation + Mock -CommandName Remove-SqlDscRSUrlReservation Mock -CommandName Set-SqlDscRSUrlReservation Mock -CommandName Invoke-RsCimMethod Mock -CommandName Invoke-RsCimMethod -MockWith $mockInvokeRsCimMethod_GenerateDatabaseCreationScript -ParameterFilter { From a78023ab991f67b3b2e22b04d2fd69716dbc6d78 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 3 Jan 2026 09:22:06 +0100 Subject: [PATCH 14/14] Replace Invoke-RsCimMethod with Remove-SqlDscRSUrlReservation in Set-TargetResource tests for accuracy --- tests/Unit/DSC_SqlRS.Tests.ps1 | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/Unit/DSC_SqlRS.Tests.ps1 b/tests/Unit/DSC_SqlRS.Tests.ps1 index a56803ad66..f5305c9ca0 100644 --- a/tests/Unit/DSC_SqlRS.Tests.ps1 +++ b/tests/Unit/DSC_SqlRS.Tests.ps1 @@ -869,13 +869,13 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { Should -Invoke -CommandName Enable-SqlDscRsSecureConnection -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'RemoveURL' -and $Arguments.Application -eq $mockReportServerApplicationName - } -Exactly -Times 2 -Scope It + Should -Invoke -CommandName Remove-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportServerApplicationName + } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'RemoveURL' -and $Arguments.Application -eq $mockReportsApplicationName - } -Exactly -Times 2 -Scope It + Should -Invoke -CommandName Remove-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportsApplicationName + } -Exactly -Times 1 -Scope It Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { $MethodName -eq 'InitializeReportServer' @@ -903,11 +903,11 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { $Application -eq $mockReportServerApplicationName - } -Exactly -Times 2 -Scope It + } -Exactly -Times 1 -Scope It Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { $Application -eq $mockReportsApplicationName - } -Exactly -Times 2 -Scope It + } -Exactly -Times 1 -Scope It Should -Invoke -CommandName Get-CimInstance -Exactly -Times 1 -Scope It Should -Invoke -CommandName Invoke-SqlDscQuery -Exactly -Times 0 -Scope It @@ -973,13 +973,13 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { Should -Invoke -CommandName Enable-SqlDscRsSecureConnection -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'RemoveURL' -and $Arguments.Application -eq $mockReportServerApplicationName - } -Exactly -Times 2 -Scope It + Should -Invoke -CommandName Remove-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportServerApplicationName + } -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { - $MethodName -eq 'RemoveURL' -and $Arguments.Application -eq $mockReportsApplicationName - } -Exactly -Times 2 -Scope It + Should -Invoke -CommandName Remove-SqlDscRSUrlReservation -ParameterFilter { + $Application -eq $mockReportsApplicationName + } -Exactly -Times 1 -Scope It Should -Invoke -CommandName Invoke-RsCimMethod -ParameterFilter { $MethodName -eq 'InitializeReportServer' @@ -1007,11 +1007,11 @@ Describe 'SqlRS\Set-TargetResource' -Tag 'Set' { Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { $Application -eq $mockReportServerApplicationName - } -Exactly -Times 2 -Scope It + } -Exactly -Times 1 -Scope It Should -Invoke -CommandName Add-SqlDscRSUrlReservation -ParameterFilter { $Application -eq $mockReportsApplicationName - } -Exactly -Times 2 -Scope It + } -Exactly -Times 1 -Scope It Should -Invoke -CommandName Get-CimInstance -Exactly -Times 1 -Scope It Should -Invoke -CommandName Invoke-SqlDscQuery -Exactly -Times 0 -Scope It