diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aebbda26..d2e4746a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ #536](https://github.com/PowerShell/xPSDesiredStateConfiguration/issues/536) and starts the deprecation process for configuring a windows firewall (exception) rule using xDSCWebService + - Fixes [issue + #463](https://github.com/PowerShell/xPSDesiredStateConfiguration/issues/463) + and fixes some bugs introduced with the new firewall rule handling ## 8.6.0.0 diff --git a/DSCPullServerSetup/PullServerDeploymentVerificationTest/PullServerSetupTests.ps1 b/DSCPullServerSetup/PullServerDeploymentVerificationTest/PullServerSetupTests.ps1 index ab4acc8a1..52b9e48c2 100644 --- a/DSCPullServerSetup/PullServerDeploymentVerificationTest/PullServerSetupTests.ps1 +++ b/DSCPullServerSetup/PullServerDeploymentVerificationTest/PullServerSetupTests.ps1 @@ -29,8 +29,8 @@ Describe PullServerInstallationTests { $DscWebConfigPath = Join-Path -Path $env:SystemDrive -ChildPath $DscWebConfigChildPath # Skip all tests if web.config is not found - if (-not (Test-Path $DscWebConfigPath)){ - Write-Error 'No pullserver web.config found.' -ErrorAction Stop + if (-not (Test-Path -Path $DscWebConfigPath)){ + throw 'No pullserver web.config found.' } # Get web.config content as XML diff --git a/DSCResources/MSFT_xDSCWebService/Firewall.psm1 b/DSCResources/MSFT_xDSCWebService/Firewall.psm1 index 2b29d21ea..45f0658aa 100644 --- a/DSCResources/MSFT_xDSCWebService/Firewall.psm1 +++ b/DSCResources/MSFT_xDSCWebService/Firewall.psm1 @@ -1,5 +1,5 @@ # Name and description for the Firewall rules. Used in multiple locations -New-Variable -Name fireWallRuleDisplayName -Value 'Desired State Configuration - Pull Server Port:{0}' -Option ReadOnly -Scope Script -Force +New-Variable -Name FireWallRuleDisplayName -Value 'DSCPullServer_IIS_Port' -Option ReadOnly -Scope Script -Force New-Variable -Name netsh -Value "$env:windir\system32\netsh.exe" -Option ReadOnly -Scope Script -Force <# .SYNOPSIS @@ -22,11 +22,13 @@ function Add-PullServerFirewallConfiguration Write-Verbose -Message 'Disable Inbound Firewall Notification' $null = & $script:netsh advfirewall set currentprofile settings inboundusernotification disable + $ruleName = $FireWallRuleDisplayName + # Remove all existing rules with that displayName - $null = & $script:netsh advfirewall firewall delete rule name=DSCPullServer_IIS_Port protocol=tcp localport=$Port + $null = & $script:netsh advfirewall firewall delete rule name=$ruleName protocol=tcp localport=$Port Write-Verbose -Message "Add Firewall Rule for port $Port" - $null = & $script:netsh advfirewall firewall add rule name=DSCPullServer_IIS_Port dir=in action=allow protocol=TCP localport=$Port + $null = & $script:netsh advfirewall firewall add rule name=$ruleName dir=in action=allow protocol=TCP localport=$Port } <# @@ -51,14 +53,17 @@ function Remove-PullServerFirewallConfiguration { # remove all existing rules with that displayName Write-Verbose -Message "Delete Firewall Rule for port $Port" - $null = & $script:netsh advfirewall firewall delete rule name=DSCPullServer_IIS_Port protocol=tcp localport=$Port + $ruleName = $FireWallRuleDisplayName # backwards compatibility with old code if (Get-Command -Name Get-NetFirewallRule -CommandType Cmdlet -ErrorAction:SilentlyContinue) { # Remove all rules with that name - $ruleName = ($($FireWallRuleDisplayName) -f $port) - Get-NetFirewallRule | Where-Object -Property DisplayName -eq -Value "$ruleName" | Remove-NetFirewallRule + Get-NetFirewallRule -DisplayName $ruleName | Remove-NetFirewallRule + } + else + { + $null = & $script:netsh advfirewall firewall delete rule name=$ruleName protocol=tcp localport=$Port } } else @@ -88,7 +93,8 @@ function Test-PullServerFirewallConfiguration # Remove all existing rules with that displayName Write-Verbose -Message "Testing Firewall Rule for port $Port" - $result = & $script:netsh advfirewall firewall show rule name=DSCPullServer_IIS_Port | Select-String -Pattern "LocalPort:\s*$Port" + $ruleName = $FireWallRuleDisplayName + $result = & $script:netsh advfirewall firewall show rule name=$ruleName | Select-String -Pattern "LocalPort:\s*$Port" return -not [string]::IsNullOrWhiteSpace($result) } diff --git a/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.psm1 b/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.psm1 index 96ddfdd3b..3a2b05202 100644 --- a/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.psm1 +++ b/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.psm1 @@ -33,6 +33,11 @@ function Get-TargetResource [System.String] $EndpointName, + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ApplicationPoolName = $DscWebServiceDefaultAppPoolName, + # Thumbprint of the Certificate in CERT:\LocalMachine\MY\ for Pull Server [Parameter(ParameterSetName = 'CertificateThumbPrint')] [ValidateNotNullOrEmpty()] @@ -73,8 +78,10 @@ function Get-TargetResource $ConfigureFirewall = $true ) - # If Certificate Subject is not specified then a value for CertificateThumbprint must be explicitly set instead. - # The Mof schema doesn't allow for a mandatory parameter in a parameter set. + <# + If Certificate Subject is not specified then a value for CertificateThumbprint must be explicitly set instead. + The Mof schema doesn't allow for a mandatory parameter in a parameter set. + #> if ($PScmdlet.ParameterSetName -eq 'CertificateThumbPrint' -and $PSBoundParameters.ContainsKey('CertificateThumbPrint') -ne $true) { throw $LocalizedData.ThrowCertificateThumbprint @@ -120,6 +127,7 @@ function Get-TargetResource $urlPrefix = $website.bindings.Collection[0].protocol + "://" $ipProperties = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties() + if ($ipProperties.DomainName) { $fqdn = '{0}.{1}' -f $ipProperties.HostName, $ipProperties.DomainName @@ -143,6 +151,7 @@ function Get-TargetResource } $ConfigureFirewall = Test-PullServerFirewallConfiguration -Port $iisPort + $ApplicationPoolName = $webSite.applicationPool } else { @@ -151,21 +160,22 @@ function Get-TargetResource } $output = @{ - EndpointName = $EndpointName - Port = $iisPort - PhysicalPath = $website.physicalPath - State = $webSite.state - DatabasePath = $databasePath - ModulePath = $modulePath - ConfigurationPath = $configurationPath - DSCServerUrl = $serverUrl - Ensure = $Ensure - RegistrationKeyPath = $registrationKeyPath - AcceptSelfSignedCertificates = $acceptSelfSignedCertificates - UseSecurityBestPractices = $UseSecurityBestPractices - DisableSecurityBestPractices = $DisableSecurityBestPractices - Enable32BitAppOnWin64 = $Enable32BitAppOnWin64 - ConfigureFirewall = $ConfigureFirewall + EndpointName = $EndpointName + ApplicationPoolName = $ApplicationPoolName + Port = $iisPort + PhysicalPath = $website.physicalPath + State = $webSite.state + DatabasePath = $databasePath + ModulePath = $modulePath + ConfigurationPath = $configurationPath + DSCServerUrl = $serverUrl + Ensure = $Ensure + RegistrationKeyPath = $registrationKeyPath + AcceptSelfSignedCertificates = $acceptSelfSignedCertificates + UseSecurityBestPractices = $UseSecurityBestPractices + DisableSecurityBestPractices = $DisableSecurityBestPractices + Enable32BitAppOnWin64 = $Enable32BitAppOnWin64 + ConfigureFirewall = $ConfigureFirewall } if ($CertificateThumbPrint -eq 'AllowUnencryptedTraffic') @@ -210,6 +220,11 @@ function Set-TargetResource [System.String] $EndpointName, + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ApplicationPoolName = $DscWebServiceDefaultAppPoolName, + # Port number of the DSC Pull Server IIS Endpoint [Parameter()] [ValidateRange(1, 65535)] @@ -240,14 +255,14 @@ function Set-TargetResource $CertificateTemplateName = 'WebServer', [Parameter()] - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [Parameter()] - [ValidateSet("Started", "Stopped")] + [ValidateSet('Started', 'Stopped')] [System.String] - $State = "Started", + $State = 'Started', # Location on the disk where the database is stored [Parameter()] @@ -293,7 +308,7 @@ function Set-TargetResource # Exceptions of security best practices [Parameter()] - [ValidateSet("SecureTLSProtocols")] + [ValidateSet('SecureTLSProtocols')] [System.String[]] $DisableSecurityBestPractices, @@ -321,7 +336,7 @@ function Set-TargetResource } # Check parameter values - if ($UseSecurityBestPractices -and ($CertificateThumbPrint -eq "AllowUnencryptedTraffic")) + if ($UseSecurityBestPractices -and ($CertificateThumbPrint -eq 'AllowUnencryptedTraffic')) { throw $LocalizedData.ThrowUseSecurityBestPractice } @@ -330,20 +345,32 @@ function Set-TargetResource { Write-Warning -Message $LocalizedData.ConfigFirewallDeprecated } + + <# + If the Pull Server Site should be bound to the non default AppPool + ensure that the AppPool already exists + #> + if ('Present' -eq $Ensure ` + -and $ApplicationPoolName -ne $DscWebServiceDefaultAppPoolName ` + -and (-not (Test-Path -Path "IIS:\AppPools\$ApplicationPoolName"))) + { + throw ($LocalizedData.ThrowApplicationPoolNotFound -f $ApplicationPoolName) + } + # Initialize with default values $pathPullServer = "$pshome\modules\PSDesiredStateConfiguration\PullServer" - $jet4provider = "System.Data.OleDb" - $jet4database = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=$DatabasePath\Devices.mdb;" - $eseprovider = "ESENT" + $jet4provider = 'System.Data.OleDb' + $jet4database = 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=$DatabasePath\Devices.mdb;' + $eseprovider = 'ESENT' $esedatabase = "$DatabasePath\Devices.edb" $cultureInfo = Get-Culture $languagePath = $cultureInfo.IetfLanguageTag $language = $cultureInfo.TwoLetterISOLanguageName - # the two letter iso languagename is not actually implemented in the source path, it's always 'en' - if (-not (Test-Path -Path $pathPullServer\$languagePath\Microsoft.Powershell.DesiredStateConfiguration.Service.Resources.dll)) + # The two letter iso languagename is not actually implemented in the source path, it's always 'en' + if (-not (Test-Path -Path "$pathPullServer\$languagePath\Microsoft.Powershell.DesiredStateConfiguration.Service.Resources.dll")) { $languagePath = 'en' } @@ -368,7 +395,6 @@ function Set-TargetResource $pswsMofFileName = "$pathPullServer\PSDSCPullServer.mof" $pswsDispatchFileName = "$pathPullServer\PSDSCPullServer.xml" - # ============ Absent block to remove existing site ========= if(($Ensure -eq "Absent")) { if(Test-Path -LiteralPath "IIS:\Sites\$EndpointName") @@ -379,109 +405,116 @@ function Set-TargetResource [System.Text.RegularExpressions.Regex]::Match($_.bindingInformation,':(\d+):').Groups[1].Value } - # there is a web site, but there shouldn't be one - Write-Verbose -Message "Removing web site $EndpointName" + # There is a web site, but there shouldn't be one + Write-Verbose -Message "Removing web site [$EndpointName]" PSWSIISEndpoint\Remove-PSWSEndpoint -SiteName $EndpointName $portList | ForEach-Object -Process { Remove-PullServerFirewallConfiguration -Port $_ } } - # we are done here, all stuff below is for 'Present' + # We are done here, all stuff below is for 'Present' return } - # =========================================================== - - Write-Verbose -Message "Create the IIS endpoint" - PSWSIISEndpoint\New-PSWSEndpoint -site $EndpointName ` - -path $PhysicalPath ` - -cfgfile $webConfigFileName ` - -port $Port ` - -applicationPoolIdentityType LocalSystem ` - -app $EndpointName ` - -svc $svcFileName ` - -mof $pswsMofFileName ` - -dispatch $pswsDispatchFileName ` - -asax "$pathPullServer\Global.asax" ` - -dependentBinaries "$pathPullServer\Microsoft.Powershell.DesiredStateConfiguration.Service.dll" ` - -language $language ` - -dependentMUIFiles "$pathPullServer\$languagePath\Microsoft.Powershell.DesiredStateConfiguration.Service.Resources.dll" ` - -certificateThumbPrint $certificateThumbPrint ` - -Enable32BitAppOnWin64 $Enable32BitAppOnWin64 ` - -Verbose - if ($ConfigureFirewall) - { - Write-Verbose -Message "Enabling firewall exception for port $port" - Add-PullServerFirewallConfiguration -Port $port - } - else + Write-Verbose -Message 'Create the IIS endpoint' + PSWSIISEndpoint\New-PSWSEndpoint ` + -site $EndpointName ` + -Path $PhysicalPath ` + -cfgfile $webConfigFileName ` + -port $Port ` + -appPool $ApplicationPoolName ` + -applicationPoolIdentityType LocalSystem ` + -app $EndpointName ` + -svc $svcFileName ` + -mof $pswsMofFileName ` + -dispatch $pswsDispatchFileName ` + -asax "$pathPullServer\Global.asax" ` + -dependentBinaries "$pathPullServer\Microsoft.Powershell.DesiredStateConfiguration.Service.dll" ` + -language $language ` + -dependentMUIFiles "$pathPullServer\$languagePath\Microsoft.Powershell.DesiredStateConfiguration.Service.Resources.dll" ` + -certificateThumbPrint $certificateThumbPrint ` + -Enable32BitAppOnWin64 $Enable32BitAppOnWin64 ` + + switch ($Ensure) { - Write-Verbose -Message "Disabling firewall exception for port $port" - Remove-PullServerFirewallConfiguration -Port $port + 'Present' + { + if ($ConfigureFirewall) + { + Write-Verbose -Message "Enabling firewall exception for port $port" + Add-PullServerFirewallConfiguration -Port $port + } + } + + 'Absent' + { + Write-Verbose -Message "Disabling firewall exception for port $port" + Remove-PullServerFirewallConfiguration -Port $port + } } Update-LocationTagInApplicationHostConfigForAuthentication -WebSite $EndpointName -Authentication "anonymous" Update-LocationTagInApplicationHostConfigForAuthentication -WebSite $EndpointName -Authentication "basic" Update-LocationTagInApplicationHostConfigForAuthentication -WebSite $EndpointName -Authentication "windows" - if($SqlProvider) + if ($SqlProvider) { - Write-Verbose -Message "Set values into the web.config that define the SQL Connection " - PSWSIISEndpoint\Set-AppSettingsInWebconfig -path $PhysicalPath -key "dbprovider" -value $jet4provider - PSWSIISEndpoint\Set-AppSettingsInWebconfig -path $PhysicalPath -key "dbconnectionstr"-value $SqlConnectionString + Write-Verbose -Message 'Set values into the web.config that define the SQL Connection' + PSWSIISEndpoint\Set-AppSettingsInWebconfig -Path $PhysicalPath -Key 'dbprovider' -Value $jet4provider + PSWSIISEndpoint\Set-AppSettingsInWebconfig -Path $PhysicalPath -Key 'dbconnectionst' -Value $SqlConnectionString if ($isBlue) { - Set-BindingRedirectSettingInWebConfig -path $PhysicalPath + Set-BindingRedirectSettingInWebConfig -Path $PhysicalPath } } elseif ($isBlue) { - Write-Verbose -Message "Set values into the web.config that define the repository for BLUE OS" - PSWSIISEndpoint\Set-AppSettingsInWebconfig -path $PhysicalPath -key "dbprovider" -value $eseprovider - PSWSIISEndpoint\Set-AppSettingsInWebconfig -path $PhysicalPath -key "dbconnectionstr"-value $esedatabase + Write-Verbose -Message 'Set values into the web.config that define the repository for BLUE OS' + PSWSIISEndpoint\Set-AppSettingsInWebconfig -Path $PhysicalPath -Key 'dbprovider' -Value $eseprovider + PSWSIISEndpoint\Set-AppSettingsInWebconfig -Path $PhysicalPath -Key 'dbconnectionstr' -Value $esedatabase - Set-BindingRedirectSettingInWebConfig -path $PhysicalPath + Set-BindingRedirectSettingInWebConfig -Path $PhysicalPath } else { - if($isDownlevelOfBlue) + if ($isDownlevelOfBlue) { - Write-Verbose -Message "Set values into the web.config that define the repository for non-BLUE Downlevel OS" - $repository = Join-Path -Path "$DatabasePath" -ChildPath "Devices.mdb" + Write-Verbose -Message 'Set values into the web.config that define the repository for non-BLUE Downlevel OS' + $repository = Join-Path -Path $DatabasePath -ChildPath 'Devices.mdb' Copy-Item -Path "$pathPullServer\Devices.mdb" -Destination $repository -Force - PSWSIISEndpoint\Set-AppSettingsInWebconfig -path $PhysicalPath -key "dbprovider" -value $jet4provider - PSWSIISEndpoint\Set-AppSettingsInWebconfig -path $PhysicalPath -key "dbconnectionstr" -value $jet4database + PSWSIISEndpoint\Set-AppSettingsInWebconfig -Path $PhysicalPath -Key 'dbprovider' -Value $jet4provider + PSWSIISEndpoint\Set-AppSettingsInWebconfig -Path $PhysicalPath -Key 'dbconnectionstr' -Value $jet4database } else { - Write-Verbose -Message "Set values into the web.config that define the repository later than BLUE OS" - Write-Verbose -Message "Only ESENT is supported on Windows Server 2016" + Write-Verbose -Message 'Set values into the web.config that define the repository later than BLUE OS' + Write-Verbose -Message 'Only ESENT is supported on Windows Server 2016' - PSWSIISEndpoint\Set-AppSettingsInWebconfig -path $PhysicalPath -key "dbprovider" -value $eseprovider - PSWSIISEndpoint\Set-AppSettingsInWebconfig -path $PhysicalPath -key "dbconnectionstr"-value $esedatabase + PSWSIISEndpoint\Set-AppSettingsInWebconfig -Path $PhysicalPath -Key 'dbprovider' -Value $eseprovider + PSWSIISEndpoint\Set-AppSettingsInWebconfig -Path $PhysicalPath -Key 'dbconnectionstr' -Value $esedatabase } } - Write-Verbose -Message "Pull Server: Set values into the web.config that indicate the location of repository, configuration, modules" + Write-Verbose -Message 'Pull Server: Set values into the web.config that indicate the location of repository, configuration, modules' # Create the application data directory calculated above - $null = New-Item -path $DatabasePath -itemType "directory" -Force + $null = New-Item -Path $DatabasePath -ItemType 'directory' -Force - $null = New-Item -path "$ConfigurationPath" -itemType "directory" -Force + $null = New-Item -Path $ConfigurationPath -ItemType 'directory' -Force - PSWSIISEndpoint\Set-AppSettingsInWebconfig -path $PhysicalPath -key "ConfigurationPath" -value $configurationPath + PSWSIISEndpoint\Set-AppSettingsInWebconfig -Path $PhysicalPath -Key 'ConfigurationPath' -Value $configurationPath - $null = New-Item -path "$ModulePath" -itemType "directory" -Force + $null = New-Item -Path $ModulePath -ItemType 'directory' -Force - PSWSIISEndpoint\Set-AppSettingsInWebconfig -path $PhysicalPath -key "ModulePath" -value $ModulePath + PSWSIISEndpoint\Set-AppSettingsInWebconfig -Path $PhysicalPath -Key 'ModulePath' -Value $ModulePath - $null = New-Item -path "$RegistrationKeyPath" -itemType "directory" -Force + $null = New-Item -Path $RegistrationKeyPath -ItemType 'directory' -Force - PSWSIISEndpoint\Set-AppSettingsInWebconfig -path $PhysicalPath -key "RegistrationKeyPath" -value $registrationKeyPath + PSWSIISEndpoint\Set-AppSettingsInWebconfig -Path $PhysicalPath -Key 'RegistrationKeyPath' -Value $registrationKeyPath - if($AcceptSelfSignedCertificates) + if ($AcceptSelfSignedCertificates) { Write-Verbose -Message 'Accepting self signed certificates from incoming hosts' Enable-IISSelfSignedModule -EndpointName $EndpointName -Enable32BitAppOnWin64:$Enable32BitAppOnWin64 @@ -491,7 +524,7 @@ function Set-TargetResource Disable-IISSelfSignedModule -EndpointName $EndpointName } - if($UseSecurityBestPractices) + if ($UseSecurityBestPractices) { UseSecurityBestPractices\Set-UseSecurityBestPractice -DisableSecurityBestPractices $DisableSecurityBestPractices } @@ -510,6 +543,11 @@ function Test-TargetResource [System.String] $EndpointName, + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ApplicationPoolName = $DscWebServiceDefaultAppPoolName, + # Port number of the DSC Pull Server IIS Endpoint [Parameter()] [System.UInt32] @@ -620,29 +658,34 @@ function Test-TargetResource :WebSiteTests Do { - Write-Verbose -Message "Check Ensure" - if(($Ensure -eq "Present" -and $null -eq $website)) + Write-Verbose -Message 'Check Ensure' + + if (($Ensure -eq 'Present' -and $null -eq $website)) { $desiredConfigurationMatch = $false Write-Verbose -Message "The Website $EndpointName is not present" break } - if(($Ensure -eq "Absent" -and $null -ne $website)) + + if (($Ensure -eq 'Absent' -and $null -ne $website)) { $desiredConfigurationMatch = $false Write-Verbose -Message "The Website $EndpointName is present but should not be" break } - if(($Ensure -eq "Absent" -and $null -eq $website)) + + if (($Ensure -eq 'Absent' -and $null -eq $website)) { $desiredConfigurationMatch = $true Write-Verbose -Message "The Website $EndpointName is not present as requested" break } - # the other case is: Ensure and exist, we continue with more checks - Write-Verbose -Message "Check Port" + # The other case is: Ensure and exist, we continue with more checks + + Write-Verbose -Message 'Check Port' $actualPort = $website.bindings.Collection[0].bindingInformation.Split(":")[1] + if ($Port -ne $actualPort) { $desiredConfigurationMatch = $false @@ -650,6 +693,15 @@ function Test-TargetResource break } + Write-Verbose -Message 'Check Application Pool' + + if ($ApplicationPoolName -ne $website.applicationPool) + { + $desiredConfigurationMatch = $false + Write-Verbose -Message "Currently bound application pool [$($website.applicationPool)] does not match the desired state [$ApplicationPoolName]." + break + } + Write-Verbose -Message 'Check Binding' $actualCertificateHash = $website.bindings.Collection[0].certificateHash $websiteProtocol = $website.bindings.collection[0].Protocol @@ -1437,7 +1489,7 @@ function Test-IISSelfSignedModuleEnabled } else { - Write-Error -Message "Website [$EndpointName] not found" + throw "Website [$EndpointName] not found" } } diff --git a/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.schema.mof b/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.schema.mof index cc9c863f8..449ab3580 100644 --- a/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.schema.mof +++ b/DSCResources/MSFT_xDSCWebService/MSFT_xDSCWebService.schema.mof @@ -10,6 +10,7 @@ class MSFT_xDSCWebService : OMI_BaseResource [write] string PhysicalPath; [write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] string Ensure; [write,ValueMap{"Started","Stopped"},Values{"Started", "Stopped"}] string State; + [write, Description("The IIS ApplicationPool to use for the Pull Server. If not specified a pool with name 'PSWS' will be created.")] string ApplicationPoolName; [write] string DatabasePath; [write] string ModulePath; [write] string ConfigurationPath; diff --git a/DSCResources/MSFT_xDSCWebService/PSWSIISEndpoint.psm1 b/DSCResources/MSFT_xDSCWebService/PSWSIISEndpoint.psm1 index 8520018cc..32ecb294c 100644 --- a/DSCResources/MSFT_xDSCWebService/PSWSIISEndpoint.psm1 +++ b/DSCResources/MSFT_xDSCWebService/PSWSIISEndpoint.psm1 @@ -1,6 +1,8 @@ # This module file contains a utility to perform PSWS IIS Endpoint setup # Module exports New-PSWSEndpoint function to perform the endpoint setup +New-Variable -Name DscWebServiceDefaultAppPoolName -Value 'PSWS' -Option ReadOnly -Force -Scope Script + <# .SYNOPSIS Validate supplied configuration to setup the PSWS Endpoint Function @@ -13,10 +15,17 @@ function Initialize-Endpoint param ( [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $appPool, + + [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $site, [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $path, @@ -30,10 +39,12 @@ function Initialize-Endpoint $port, [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $app, [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $applicationPoolIdentityType, @@ -48,6 +59,7 @@ function Initialize-Endpoint $mof, [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $dispatch, @@ -57,14 +69,17 @@ function Initialize-Endpoint $asax, [Parameter()] + [ValidateNotNullOrEmpty()] [System.String[]] $dependentBinaries, [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $language, [Parameter()] + [ValidateNotNullOrEmpty()] [System.String[]] $dependentMUIFiles, @@ -101,14 +116,11 @@ function Initialize-Endpoint Test-IISInstall - $appPool = 'PSWS' - - Write-Verbose -Message 'Delete the App Pool if it exists' - Remove-AppPool -apppool $appPool - - Write-Verbose -Message 'Remove the site if it already exists' + # First remove the site so that the binding count on the application pool is reduced Update-Site -siteName $site -siteAction Remove + Remove-AppPool -appPool $appPool + # Check for existing binding, there should be no binding with the same port $allWebBindingsOnPort = Get-WebBinding | Where-Object -FilterScript { $_.BindingInformation -eq "*:$($port):" @@ -127,9 +139,25 @@ function Initialize-Endpoint } } - Copy-PSWSConfigurationToIISEndpointFolder -path $path -cfgfile $cfgfile -svc $svc -mof $mof -dispatch $dispatch -asax $asax -dependentBinaries $dependentBinaries -language $language -dependentMUIFiles $dependentMUIFiles -psFiles $psFiles - - New-IISWebSite -site $site -path $path -port $port -app $app -apppool $appPool -applicationPoolIdentityType $applicationPoolIdentityType -certificateThumbPrint $certificateThumbPrint -enable32BitAppOnWin64 $enable32BitAppOnWin64 + Copy-PSWSConfigurationToIISEndpointFolder -path $path ` + -cfgfile $cfgfile ` + -svc $svc ` + -mof $mof ` + -dispatch $dispatch ` + -asax $asax ` + -dependentBinaries $dependentBinaries ` + -language $language ` + -dependentMUIFiles $dependentMUIFiles ` + -psFiles $psFiles + + New-IISWebSite -site $site ` + -path $path ` + -port $port ` + -app $app ` + -apppool $appPool ` + -applicationPoolIdentityType $applicationPoolIdentityType ` + -certificateThumbPrint $certificateThumbPrint ` + -enable32BitAppOnWin64 $enable32BitAppOnWin64 } <# @@ -205,27 +233,95 @@ function Update-Site [Parameter(ParameterSetName = 'SiteName', Mandatory = $true, Position = 1)] [Parameter(ParameterSetName = 'Site', Mandatory = $true, Position = 1)] [System.String] + [ValidateSet('Start', 'Stop', 'Remove')] $siteAction ) - [System.String] $name = $null + if ('SiteName' -eq $PSCmdlet.ParameterSetName) + { + $site = Get-Website -Name $siteName + } - if ($PSCmdlet.ParameterSetName -eq 'SiteName') + if ($site) { - $name = $siteName + switch ($siteAction) + { + 'Start' + { + Write-Verbose -Message "Starting IIS Website [$($site.name)]" + Start-Website -Name $site.name + } + + 'Stop' + { + if ('Started' -eq $site.state) + { + Write-Verbose -Message "Stopping WebSite $($site.name)" + $website = Stop-Website -Name $site.name -Passthru + + if ('Started' -eq $website.state) + { + throw "Unable to stop WebSite $($site.name)" + } + + <# + There may be running requests, wait a little + I had an issue where the files were still in use + when I tried to delete them + #> + Write-Verbose -Message 'Waiting for IIS to stop website' + Start-Sleep -Milliseconds 1000 + } + else + { + Write-Verbose -Message "IIS Website [$($site.name)] already stopped" + } + } + + 'Remove' + { + Update-Site -site $site -siteAction Stop + Write-Verbose -Message "Removing IIS Website [$($site.name)]" + Remove-Website -Name $site.name + } + } } - elseif ($PSCmdlet.ParameterSetName -eq 'Site') + else { - $name = $site.Name + Write-Verbose -Message "IIS Website [$siteName] not found" } +} + +<# + .SYNOPSIS + Returns the list of bound sites and applications for a given IIS Application pool + + .PARAMETER appPool + The application pool name +#> +function Get-AppPoolBinding +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $AppPool + ) - if (Test-ForIISSite -siteName $name) + if (Test-Path -Path "IIS:\AppPools\$AppPool") { - switch ($siteAction) - { - 'Start' {Start-Website -Name "$name"} - 'Stop' {Stop-Website -Name "$name" -ErrorAction SilentlyContinue} - 'Remove' {Remove-Website -Name "$name"} + $sites = Get-WebConfigurationProperty ` + -Filter "/system.applicationHost/sites/site/application[@applicationPool=`'$AppPool`'and @path='/']/parent::*" ` + -PSPath 'machine/webroot/apphost' ` + -Name name + $apps = Get-WebConfigurationProperty ` + -Filter "/system.applicationHost/sites/site/application[@applicationPool=`'$AppPool`'and @path!='/']" ` + -PSPath 'machine/webroot/apphost' ` + -Name path + $sites, $apps | ForEach-Object { + $_.Value } } } @@ -240,15 +336,32 @@ function Remove-AppPool [CmdletBinding()] param ( - [Parameter()] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] - $appPool + $AppPool ) - # Without this tests we may get a breaking error here, despite SilentlyContinue - if (Test-Path -Path "IIS:\AppPools\$appPool") + if ($DscWebServiceDefaultAppPoolName -eq $AppPool) { - Remove-WebAppPool -Name $appPool -ErrorAction SilentlyContinue + # Without this tests we may get a breaking error here, despite SilentlyContinue + if (Test-Path -Path "IIS:\AppPools\$AppPool") + { + $bindingCount = (Get-AppPoolBinding -AppPool $AppPool | Measure-Object).Count + + if (0 -ge $bindingCount) + { + Remove-WebAppPool -Name $AppPool -ErrorAction SilentlyContinue + } + else + { + Write-Verbose -Message "Application pool [$AppPool] can't be deleted because it's still bound to a site or application" + } + } + } + else + { + Write-Verbose -Message "ApplicationPool can't be deleted because the name is different from built-in name [$DscWebServiceDefaultAppPoolName]." } } @@ -371,23 +484,27 @@ function New-IISWebSite [CmdletBinding()] param ( - [Parameter()] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $site, - [Parameter()] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $path, - [Parameter()] + [Parameter(Mandatory = $true)] [System.Int32] $port, - [Parameter()] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $app, - [Parameter()] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $appPool, @@ -395,7 +512,8 @@ function New-IISWebSite [System.String] $applicationPoolIdentityType, - [Parameter()] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $certificateThumbPrint, @@ -406,35 +524,59 @@ function New-IISWebSite $siteID = New-SiteID - Write-Verbose -Message 'Adding App Pool' - $null = New-WebAppPool -Name $appPool - - Write-Verbose -Message 'Set App Pool Properties' - $appPoolIdentity = 4 - if ($applicationPoolIdentityType) + if (Test-Path IIS:\AppPools\$appPool) { - # LocalSystem = 0, LocalService = 1, NetworkService = 2, SpecificUser = 3, ApplicationPoolIdentity = 4 - if ($applicationPoolIdentityType -eq 'LocalSystem') - { - $appPoolIdentity = 0 - } - elseif ($applicationPoolIdentityType -eq 'LocalService') - { - $appPoolIdentity = 1 - } - elseif ($applicationPoolIdentityType -eq 'NetworkService') + Write-Verbose -Message "Application Pool [$appPool] already exists" + } + else + { + Write-Verbose -Message "Adding App Pool [$appPool]" + $null = New-WebAppPool -Name $appPool + + Write-Verbose -Message 'Set App Pool Properties' + $appPoolIdentity = 4 + + if ($applicationPoolIdentityType) { - $appPoolIdentity = 2 + # LocalSystem = 0, LocalService = 1, NetworkService = 2, SpecificUser = 3, ApplicationPoolIdentity = 4 + switch ($applicationPoolIdentityType) + { + 'LocalSystem' + { + $appPoolIdentity = 0 + } + + 'LocalService' + { + $appPoolIdentity = 1 + } + + 'NetworkService' + { + $appPoolIdentity = 2 + } + + 'ApplicationPoolIdentity' + { + $appPoolIdentity = 4 + } + + default { + throw "Invalid value [$applicationPoolIdentityType] for parameter -applicationPoolIdentityType" + } + } } - } - $appPoolItem = Get-Item IIS:\AppPools\$appPool - $appPoolItem.managedRuntimeVersion = 'v4.0' - $appPoolItem.enable32BitAppOnWin64 = $enable32BitAppOnWin64 - $appPoolItem.processModel.identityType = $appPoolIdentity - $appPoolItem | Set-Item + $appPoolItem = Get-Item -Path IIS:\AppPools\$appPool + $appPoolItem.managedRuntimeVersion = 'v4.0' + $appPoolItem.enable32BitAppOnWin64 = $enable32BitAppOnWin64 + $appPoolItem.processModel.identityType = $appPoolIdentity + $appPoolItem | Set-Item + + } Write-Verbose -Message 'Add and Set Site Properties' + if ($certificateThumbPrint -eq 'AllowUnencryptedTraffic') { $null = New-WebSite -Name $site -Id $siteID -Port $port -IPAddress "*" -PhysicalPath $path -ApplicationPool $appPool @@ -506,16 +648,19 @@ function New-PSWSEndpoint ( # Unique Name of the IIS Site [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $site = 'PSWS', # Physical path for the IIS Endpoint on the machine (under inetpub) [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $path = "$env:SystemDrive\inetpub\PSWS", # Web.config file [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $cfgfile = 'web.config', @@ -526,9 +671,15 @@ function New-PSWSEndpoint # IIS Application Name for the Site [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $app = 'PSWS', + # IIS Application Name for the Site + [Parameter()] + [System.String] + $appPool, + # IIS App Pool Identity Type - must be one of LocalService, LocalSystem, NetworkService, ApplicationPoolIdentity [Parameter()] [ValidateSet('LocalService', 'LocalSystem', 'NetworkService', 'ApplicationPoolIdentity')] @@ -537,6 +688,7 @@ function New-PSWSEndpoint # WCF Service SVC file [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $svc = 'PSWS.svc', @@ -578,7 +730,6 @@ function New-PSWSEndpoint # Any dependent PowerShell Scipts/Modules that need to be deployed to the IIS endpoint application root [Parameter()] - [ValidateNotNullOrEmpty()] [System.String[]] $psFiles, @@ -603,10 +754,16 @@ function New-PSWSEndpoint $Enable32BitAppOnWin64 = $false ) + if (-not $appPool) + { + $appPool = $DscWebServiceDefaultAppPoolName + } + $script:wevtutil = "$env:windir\system32\Wevtutil.exe" $svcName = Split-Path $svc -Leaf $protocol = 'https:' + if ($certificateThumbPrint -eq 'AllowUnencryptedTraffic') { $protocol = 'http:' @@ -615,13 +772,26 @@ function New-PSWSEndpoint # Get Machine Name $cimInstance = Get-CimInstance -ClassName Win32_ComputerSystem -Verbose:$false - Write-Verbose ("Setting up endpoint at - $protocol//" + $cimInstance.Name + ':' + $port + '/' + $svcName) - Initialize-Endpoint -site $site -path $path -cfgfile $cfgfile -port $port -app $app ` - -applicationPoolIdentityType $applicationPoolIdentityType -svc $svc -mof $mof ` - -dispatch $dispatch -asax $asax -dependentBinaries $dependentBinaries ` - -language $language -dependentMUIFiles $dependentMUIFiles -psFiles $psFiles ` - -removeSiteFiles $removeSiteFiles -certificateThumbPrint $certificateThumbPrint ` - -enable32BitAppOnWin64 $Enable32BitAppOnWin64 + Write-Verbose -Message "Setting up endpoint at - $protocol//$($cimInstance.Name):$port/$svcName" + Initialize-Endpoint ` + -appPool $appPool ` + -site $site ` + -path $path ` + -cfgfile $cfgfile ` + -port $port ` + -app $app ` + -applicationPoolIdentityType $applicationPoolIdentityType ` + -svc $svc ` + -mof $mof ` + -dispatch $dispatch ` + -asax $asax ` + -dependentBinaries $dependentBinaries ` + -language $language ` + -dependentMUIFiles $dependentMUIFiles ` + -psFiles $psFiles ` + -removeSiteFiles $removeSiteFiles ` + -certificateThumbPrint $certificateThumbPrint ` + -enable32BitAppOnWin64 $Enable32BitAppOnWin64 if ($EnablePSWSETW) { @@ -655,48 +825,25 @@ function Remove-PSWSEndpoint # Get the site to remove $site = Get-Website -Name $siteName + if ($site) { - if ('Started' -eq $site.state) - { - Write-Verbose -Message "Stopping WebSite $($site.name)" - $website = Stop-Website -Name $site.name -Passthru - if ('Started' -eq $website.state) - { - Write-Error -Message "Unable to stop WebSite $($site.name)" -ErrorAction:Stop - } - } - # And the pool it is using $pool = $site.applicationPool - # Get the path so we can delete the files $filePath = $site.PhysicalPath # Remove the actual site. - Remove-Website -Name $siteName - <# - There may be running requests, wait a little - I had an issue where the files were still in use - when I tried to delete them - #> - Start-Sleep -Milliseconds 2000 + Update-Site -site $site -siteAction Remove # Remove the files for the site - If (Test-Path -Path $filePath) + if (Test-Path -Path $filePath) { Get-ChildItem -Path $filePath -Recurse | Remove-Item -Recurse -Force Remove-Item -Path $filePath -Force } - # Find out whether any other site is using this pool - $filter = "/system.applicationHost/sites/site/application[@applicationPool='" + $pool + "']" - $apps = (Get-WebConfigurationProperty -Filter $filter -PSPath 'machine/webroot/apphost' -name path).ItemXPath - if (-not $apps -or $apps.count -eq 1) - { - # If we are the only site in the pool, remove the pool as well. - Remove-WebAppPool -Name $pool - } + Remove-AppPool -appPool $pool } else { @@ -721,22 +868,22 @@ function Set-AppSettingsInWebconfig [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] - $path, + $Path, # Key to add/update [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] - $key, + $Key, # Value [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] - $value + $Value ) - $webconfig = Join-Path $path 'web.config' + $webconfig = Join-Path -Path $Path -ChildPath 'web.config' [System.Boolean] $Found = $false if (Test-Path -Path $webconfig) @@ -744,24 +891,24 @@ function Set-AppSettingsInWebconfig $xml = [System.Xml.XmlDocument] (Get-Content -Path $webconfig) $root = $xml.get_DocumentElement() - foreach( $item in $root.appSettings.add) + foreach ($item in $root.appSettings.add) { - if( $item.key -eq $key ) + if ($item.key -eq $Key) { - $item.value = $value; + $item.value = $Value; $Found = $true; } } - if( -not $Found) + if (-not $Found) { $newElement = $xml.CreateElement('add') $nameAtt1 = $xml.CreateAttribute('key') - $nameAtt1.psbase.value = $key; + $nameAtt1.psbase.value = $Key; $null = $newElement.SetAttributeNode($nameAtt1) $nameAtt2 = $xml.CreateAttribute('value') - $nameAtt2.psbase.value = $value; + $nameAtt2.psbase.value = $Value; $null = $newElement.SetAttributeNode($nameAtt2) $null = $xml.configuration['appSettings'].AppendChild($newElement) @@ -867,4 +1014,6 @@ function Set-BindingRedirectSettingInWebConfig } } -Export-ModuleMember -function New-PSWSEndpoint, Set-AppSettingsInWebconfig, Set-BindingRedirectSettingInWebConfig, Remove-PSWSEndpoint +Export-ModuleMember ` + -Function New-PSWSEndpoint, Set-AppSettingsInWebconfig, Set-BindingRedirectSettingInWebConfig, Remove-PSWSEndpoint ` + -Variable DscWebServiceDefaultAppPoolName diff --git a/DSCResources/MSFT_xDSCWebService/en-US/MSFT_xDSCWebService.psd1 b/DSCResources/MSFT_xDSCWebService/en-US/MSFT_xDSCWebService.psd1 index ed91aeded..28cdc704e 100644 --- a/DSCResources/MSFT_xDSCWebService/en-US/MSFT_xDSCWebService.psd1 +++ b/DSCResources/MSFT_xDSCWebService/en-US/MSFT_xDSCWebService.psd1 @@ -7,4 +7,5 @@ ConvertFrom-StringData -StringData @' IISInstallationPathNotFound = IIS installation path not found IISWebAdministrationAssemblyNotFound = IIS version of Microsoft.Web.Administration.dll not found ConfigFirewallDeprecated = The support for configuring firewall rules is deprecated. Please set ConfigureFirewall to false and use the Firewall resource from NetworkingDSC module to configure required firewall rules. + ThrowApplicationPoolNotFound = IIS Application pool "{0}" not found. '@ diff --git a/Examples/Sample_xDscWebServiceRegistration.ps1 b/Examples/Sample_xDscWebServiceRegistration.ps1 index 259cade4a..9208c95a6 100644 --- a/Examples/Sample_xDscWebServiceRegistration.ps1 +++ b/Examples/Sample_xDscWebServiceRegistration.ps1 @@ -133,77 +133,3 @@ Configuration Sample_xDscWebServiceRegistration } } } - -<# - .SYNOPSIS - The Sample_MetaConfigurationToRegisterWithSecurePullServer registers - a DSC client node with the pull server. - - .PARAMETER NodeName - The name of the node being configured as a DSC Pull Server. - - .PARAMETER RegistrationKey - This key will be used by client nodes as a shared key to authenticate - during registration. This should be a string with enough entropy - (randomness) to protect the registration of clients to the pull server. - The example creates a new GUID for the registration key. - - .PARAMETER ServerName - The HostName to use when configuring the Pull Server URL on the DSC - client. - - .PARAMETER Port - The port on which the PullServer is listening for connections - - .EXAMPLE - $registrationKey = [System.Guid]::NewGuid() - - Sample_MetaConfigurationToRegisterWithSecurePullServer -RegistrationKey $registrationKey -#> -[DSCLocalConfigurationManager()] -Configuration Sample_MetaConfigurationToRegisterWithSecurePullServer -{ - param - ( - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $NodeName = 'localhost', - - [Parameter()] - [Parameter(Mandatory = $true)] - [System.String] - $RegistrationKey, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ServerName = 'localhost', - - [Parameter()] - [ValidateRange(1, 65535)] - [System.UInt16] - $Port = 8080 - ) - - Node $NodeName - { - Settings - { - RefreshMode = 'Pull' - } - - ConfigurationRepositoryWeb CONTOSO-PullSrv - { - ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" - RegistrationKey = $RegistrationKey - ConfigurationNames = @('ClientConfig') - } - - ReportServerWeb CONTOSO-PullSrv - { - ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" - RegistrationKey = $RegistrationKey - } - } -} diff --git a/Examples/Sample_xDscWebServiceRegistrationWithSecurityBestPractices.ps1 b/Examples/Sample_xDscWebServiceRegistrationWithSecurityBestPractices.ps1 index a082e71e3..dee5f5f3e 100644 --- a/Examples/Sample_xDscWebServiceRegistrationWithSecurityBestPractices.ps1 +++ b/Examples/Sample_xDscWebServiceRegistrationWithSecurityBestPractices.ps1 @@ -136,69 +136,3 @@ Configuration Sample_xDscWebServiceRegistrationWithSecurityBestPractices } } } - -<# - .SYNOPSIS - The Sample_MetaConfigurationToRegisterWithSecurePullServer registers - a DSC client node with the pull server. - - .PARAMETER NodeName - The name of the node being configured as a DSC Pull Server. - - .PARAMETER RegistrationKey - This key will be used by client nodes as a shared key to authenticate - during registration. This should be a string with enough entropy - (randomness) to protect the registration of clients to the pull server. - The example creates a new GUID for the registration key. - - .PARAMETER ServerName - The HostName to use when configuring the Pull Server URL on the DSC - client. - - .EXAMPLE - $registrationKey = [System.Guid]::NewGuid() - - Sample_MetaConfigurationToRegisterWithSecurePullServer -RegistrationKey $registrationKey -#> -[DSCLocalConfigurationManager()] -Configuration Sample_MetaConfigurationToRegisterWithSecurePullServer -{ - param - ( - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $NodeName = 'localhost', - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $RegistrationKey, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ServerName = 'localhost' - ) - - Node $NodeName - { - Settings - { - RefreshMode = 'Pull' - } - - ConfigurationRepositoryWeb CONTOSO-PullSrv - { - ServerURL = "https://$ServerName`:8080/PSDSCPullServer.svc" - RegistrationKey = $RegistrationKey - ConfigurationNames = @('ClientConfig') - } - - ReportServerWeb CONTOSO-PullSrv - { - ServerURL = "https://$ServerName`:8080/PSDSCPullServer.svc" - RegistrationKey = $RegistrationKey - } - } -} diff --git a/Examples/Sample_xDscWebServiceRegistration_UseSQLProvider.ps1 b/Examples/Sample_xDscWebServiceRegistration_UseSQLProvider.ps1 index 7df76c005..9e7e9f42e 100644 --- a/Examples/Sample_xDscWebServiceRegistration_UseSQLProvider.ps1 +++ b/Examples/Sample_xDscWebServiceRegistration_UseSQLProvider.ps1 @@ -134,74 +134,3 @@ Configuration Sample_xDscWebServiceRegistration_UseSQLProvider } } - -<# - .SYNOPSIS - The Sample_MetaConfigurationToRegisterWithSecurePullServer registers - a DSC client node with the pull server. - - .PARAMETER NodeName - The name of the node being configured as a DSC Pull Server. - - .PARAMETER RegistrationKey - This key will be used by client nodes as a shared key to authenticate - during registration. This should be a string with enough entropy - (randomness) to protect the registration of clients to the pull server. - The example creates a new GUID for the registration key. - - .PARAMETER ServerName - The HostName to use when configuring the Pull Server URL on the DSC - client. - - .EXAMPLE - $registrationKey = [System.Guid]::NewGuid() - - Sample_MetaConfigurationToRegisterWithSecurePullServer -RegistrationKey $registrationKey -#> -[DSCLocalConfigurationManager()] -Configuration Sample_MetaConfigurationToRegisterWithSecurePullServer -{ - param - ( - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $NodeName = 'localhost', - - [Parameter()] - [Parameter(Mandatory = $true)] - [System.String] - $RegistrationKey, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ServerName = 'localhost', - - [Parameter()] - [ValidateRange(1, 65535)] - [System.UInt16] - $Port = 8080 - ) - - Node $NodeName - { - Settings - { - RefreshMode = 'Pull' - } - - ConfigurationRepositoryWeb CONTOSO-PullSrv - { - ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" - RegistrationKey = $RegistrationKey - ConfigurationNames = @('ClientConfig') - } - - ReportServerWeb CONTOSO-PullSrv - { - ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" - RegistrationKey = $RegistrationKey - } - } -} diff --git a/Examples/Sample_xDscWebServiceRegistration_Win2k12and2k12R2.ps1 b/Examples/Sample_xDscWebServiceRegistration_Win2k12and2k12R2.ps1 index de9f14db6..3ba4ac0c8 100644 --- a/Examples/Sample_xDscWebServiceRegistration_Win2k12and2k12R2.ps1 +++ b/Examples/Sample_xDscWebServiceRegistration_Win2k12and2k12R2.ps1 @@ -117,74 +117,3 @@ Configuration Sample_xDscWebServiceRegistration_Win2k12and2k12R2 } } } - -<# - .SYNOPSIS - The Sample_MetaConfigurationToRegisterWithSecurePullServer registers - a DSC client node with the pull server. - - .PARAMETER NodeName - The name of the node being configured as a DSC Pull Server. - - .PARAMETER RegistrationKey - This key will be used by client nodes as a shared key to authenticate - during registration. This should be a string with enough entropy - (randomness) to protect the registration of clients to the pull server. - The example creates a new GUID for the registration key. - - .PARAMETER ServerName - The HostName to use when configuring the Pull Server URL on the DSC - client. - - .EXAMPLE - $registrationKey = [System.Guid]::NewGuid() - - Sample_MetaConfigurationToRegisterWithSecurePullServer -RegistrationKey $registrationKey -#> -[DSCLocalConfigurationManager()] -Configuration Sample_MetaConfigurationToRegisterWithSecurePullServer -{ - param - ( - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $NodeName = 'localhost', - - [Parameter()] - [Parameter(Mandatory = $true)] - [System.String] - $RegistrationKey, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ServerName = 'localhost', - - [Parameter()] - [ValidateRange(1, 65535)] - [System.UInt16] - $Port = 8080 - ) - - Node $NodeName - { - Settings - { - RefreshMode = 'Pull' - } - - ConfigurationRepositoryWeb CONTOSO-PullSrv - { - ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" - RegistrationKey = $RegistrationKey - ConfigurationNames = @('ClientConfig') - } - - ReportServerWeb CONTOSO-PullSrv - { - ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" - RegistrationKey = $RegistrationKey - } - } -} diff --git a/Examples/Sample_xDscWebService_Client.ps1 b/Examples/Sample_xDscWebService_Client.ps1 new file mode 100644 index 000000000..733d270dd --- /dev/null +++ b/Examples/Sample_xDscWebService_Client.ps1 @@ -0,0 +1,86 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 119f0689-7410-4b2d-a805-d5df9f582cad +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/xPSDesiredStateConfiguration +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +#> +<# + .SYNOPSIS + The Sample_xDscWebService_Client registers + a DSC client node with the pull server. + + .PARAMETER NodeName + The name of the node being configured as a DSC Pull Server. + + .PARAMETER RegistrationKey + This key will be used by client nodes as a shared key to authenticate + during registration. This should be a string with enough entropy + (randomness) to protect the registration of clients to the pull server. + The example creates a new GUID for the registration key. + + .PARAMETER ServerName + The HostName to use when configuring the Pull Server URL on the DSC + client. + + .PARAMETER Port + The port on which the PullServer is listening for connections + + .EXAMPLE + $registrationKey = [System.Guid]::NewGuid() + + Sample_xDscWebService_Client -RegistrationKey $registrationKey +#> +[DSCLocalConfigurationManager()] +Configuration Sample_xDscWebService_Client +{ + param + ( + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [System.String] + $RegistrationKey, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerName = 'localhost', + + [Parameter()] + [ValidateRange(1, 65535)] + [System.UInt16] + $Port = 8080 + ) + + Node $NodeName + { + Settings + { + RefreshMode = 'Pull' + } + + ConfigurationRepositoryWeb CONTOSO-PullSrv + { + ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" + RegistrationKey = $RegistrationKey + ConfigurationNames = @('ClientConfig') + } + + ReportServerWeb CONTOSO-PullSrv + { + ServerURL = "https://$ServerName`:$Port/PSDSCPullServer.svc" + RegistrationKey = $RegistrationKey + } + } +} diff --git a/Examples/Sample_xDscWebService_Preferred.ps1 b/Examples/Sample_xDscWebService_Preferred.ps1 new file mode 100644 index 000000000..430e75e35 --- /dev/null +++ b/Examples/Sample_xDscWebService_Preferred.ps1 @@ -0,0 +1,153 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 4321b681-da05-4486-a7db-1ce4842d40c5 +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/xPSDesiredStateConfiguration +.ICONURI +.EXTERNALMODULEDEPENDENCIES NetworkingDsc, xPSDesiredStateConfiguration, xWebAdministration +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +#> +<# + .SYNOPSIS + Configures a DSC Pull Server with an separately configured IIS Application Pool + + .DESCRIPTION + The Sample_xDscWebServiceRegistration configuration sets up a DSC pull + server that is capable for client nodes to register with it and + retrieve configuration documents with configuration names instead of + configuration id. + + Prerequisite: 1 - Install a certificate in 'CERT:\LocalMachine\MY\' store + For testing environments, you could use a self-signed + certificate. (New-SelfSignedCertificate cmdlet could + generate one for you). For production environments, you + will need a certificate signed by valid CA. Registration + only works over https protocols. So to use registration + feature, a secure pull server setup with certificate is + necessary. + 2 - To configure a Firewall Rule (Exception) to allow external + connections the [NetworkingDsc](https://github.com/PowerShell/NetworkingDsc) + DSC module is required. + 3 - The [xWebAdministration](https://github.com/PowerShell/xWebAdministration) + DSC module is required to configure the IIS Application Pool + + .PARAMETER NodeName + The name of the node being configured as a DSC Pull Server. + + .PARAMETER CertificateThumbPrint + Certificate thumbprint for creating an HTTPS endpoint. Use + "AllowUnencryptedTraffic" for setting up a non SSL based endpoint. + + .PARAMETER RegistrationKey + This key will be used by client nodes as a shared key to authenticate + during registration. This should be a string with enough entropy + (randomness) to protect the registration of clients to the pull server. + The example creates a new GUID for the registration key. + + .PARAMETER Port + The TCP port on which the Pull Server will listen for connections + + .PARAMETER ApplicationPoolName + The IIS Application Pool to use with the new Pull Server + + .EXAMPLE + $thumbprint = (New-SelfSignedCertificate -DnsName $env:COMPUTERNAME -CertStoreLocation Cert:\LocalMachine\My).Thumbprint + $registrationKey = [System.Guid]::NewGuid() + + Sample_xDscWebService_Preferred -RegistrationKey $registrationkey -CertificateThumbPrint $thumbprint +#> +Configuration Sample_xDscWebService_Preferred +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $CertificateThumbPrint, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $RegistrationKey, + + [Parameter()] + [ValidateRange(1, 65535)] + [System.UInt16] + $Port = 8080, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ApplicationPoolName + ) + + Import-DscResource -ModuleName 'NetworkingDsc' + Import-DSCResource -ModuleName 'xPSDesiredStateConfiguration' + Import-DscResource -ModuleName 'xWebAdministration' + + Node $NodeName + { + WindowsFeature DSCServiceFeature + { + Ensure = 'Present' + Name = 'DSC-Service' + } + + xWebAppPool PSDSCPullServerPool + { + Ensure = 'Present' + Name = $ApplicationPoolName + IdentityType = 'NetworkService' + } + + xDscWebService PSDSCPullServer + { + Ensure = 'Present' + EndpointName = 'PSDSCPullServer' + ApplicationPoolName = $ApplicationPoolName + Port = $Port + PhysicalPath = "$env:SystemDrive\inetpub\PSDSCPullServer" + CertificateThumbPrint = $CertificateThumbPrint + ModulePath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules" + ConfigurationPath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration" + State = 'Started' + RegistrationKeyPath = "$env:PROGRAMFILES\WindowsPowerShell\DscService" + AcceptSelfSignedCertificates = $true + Enable32BitAppOnWin64 = $false + UseSecurityBestPractices = $true + ConfigureFirewall = $false + DependsOn = '[WindowsFeature]DSCServiceFeature', '[xWebAppPool]PSDSCPullServerPool' + } + + File RegistrationKeyFile + { + Ensure = 'Present' + Type = 'File' + DestinationPath = "$env:ProgramFiles\WindowsPowerShell\DscService\RegistrationKeys.txt" + Contents = $RegistrationKey + } + + Firewall PSDSCPullServerRule + { + Ensure = 'Present' + Name = "DSC_PullServer_$Port" + DisplayName = "DSC PullServer $Port" + Group = 'DSC PullServer' + Enabled = $true + Action = 'Allow' + Direction = 'InBound' + LocalPort = $Port + Protocol = 'TCP' + DependsOn = '[xDscWebService]PSDSCPullServer' + } + } +} diff --git a/README.md b/README.md index 2f17865e0..d8a43fce7 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,12 @@ None endpoint. Use "AllowUnencryptedTraffic" for setting up a non SSL based endpoint. * **Port**: Port for web service. +* **ApplicationPoolName**: The name of IIS ApplicationPool to use for the Pull + Server. If not specified a pool with name 'PSWS' will be created and bound to + the Pull Server instance. Preferably the new application pool is created by + using the __xWebAppPool__ resource from the + [xWebAdministration](https://github.com/PowerShell/xWebAdministration) DSC + module. * **PhysicalPath**: Folder location where the content of the web service resides. * **Ensure**: Ensures that the web service is **Present** or **Absent** @@ -209,6 +215,22 @@ All users are requested to adjust existing configurations so that the is created by using the **Firewall** resource from the [NetworkingDsc](https://github.com/PowerShell/NetworkingDsc) module. +#### Examples + +##### Pull Server + +* [A Pull Server with a separately defined IIS Application Pool and firewall + rule](./Examples/Sample_xDscWebService_Preferred.ps1) +* [A Pull Server using a SQL Server as backend](./Examples/Sample_xDscWebServiceRegistration_UseSQLProvider.ps1) +* [A Pull Server default configuration](./Examples/Sample_xDscWebServiceRegistration_Win2k12and2k12R2.ps1) +* [A Pull Server with an enhanced security configuration and a firewall rule](./Examples/Sample_xDscWebServiceRegistration.ps1) +* [A Pull Server using the security best practice configuration](./Examples/Sample_xDscWebServiceRegistrationWithSecurityBestPractices.ps1) +* [Removing a Pull Server instance](./Examples/Sample_xDscWebServiceRemoval.ps1) + +##### Client + +* [Common Client (Server) Configuration](./Examples/Sample_xDscWebService_Client.ps1) + ### xGroup Provides a mechanism to manage local groups on the target node. diff --git a/Tests/Integration/MSFT_xDSCWebService.Integration.tests.ps1 b/Tests/Integration/MSFT_xDSCWebService.Integration.tests.ps1 index 59a9b929d..95c949c3b 100644 --- a/Tests/Integration/MSFT_xDSCWebService.Integration.tests.ps1 +++ b/Tests/Integration/MSFT_xDSCWebService.Integration.tests.ps1 @@ -14,7 +14,7 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR # Ensure that Powershell Module 'WebAdministration' is available if (-not (Install-WindowsFeatureAndVerify -Name Web-Mgmt-Tools)) { - Write-Error -Message 'Failed to verify for required Windows Feature. Unable to continue ...' -ErrorAction:Stop + throw 'Failed to verify for required Windows Feature. Unable to continue ...' } Import-Module -Name WebAdministration -ErrorAction:Stop -Force Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -ErrorAction:Stop -Force @@ -30,6 +30,20 @@ if (Test-SkipContinuousIntegrationTask -Type 'Integration') return } +$configurationFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dcsResourceName).config.ps1" + +if ($env:CI -eq $false) +{ + # Install modules + $requiredModules = Get-ResourceModulesInConfiguration -ConfigurationPath $configurationFile | + Where-Object -Property Name -ne $script:dscModuleName + + if ($requiredModules) + { + Install-DependentModule -Module $requiredModules + } +} + <# .SYNOPSIS Performs common DSC integration tests including compiling, setting, @@ -81,6 +95,45 @@ function Invoke-CommonResourceTesting } } +<# + .SYNOPSIS + Tests if the specified IIS application pool is absent or present + + .PARAMETER ApplicationPoolName + name of the IIS application pool + + .PARAMETER ResourceState + state of the IIS application pool +#> +function Test-IISApplicationPool +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ApplicationPoolName, + + [Parameter(Mandatory = $true)] + [ValidateSet('Present', 'Absent')] + [System.String] + $ResourceState + ) + + $appPoolPath = Join-Path -Path 'IIS:\AppPools' -ChildPath $ApplicationPoolName + + switch ($ResourceState) + { + 'Present' { + Test-Path -Path $appPoolPath + } + 'Absent' { + -not (Test-Path -Path $appPoolPath) + } + } +} + <# .SYNOPSIS Performs common tests to ensure that the DSC pull server was properly @@ -94,6 +147,9 @@ function Invoke-CommonResourceTesting .PARAMETER WebsiteState State of the website + + .PARAMETER ApplicationPoolName + name of the IIS application pool #> function Test-DSCPullServer { @@ -113,16 +169,21 @@ function Test-DSCPullServer [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] - $WebsiteState - ) + $WebsiteState, - It 'Should create a web.config file at the web site root' { - Test-Path -Path (Join-Path -Path $ConfigurationData.AllNodes.PhysicalPath -ChildPath 'web.config') | Should -Be $true - } + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ApplicationPoolName = 'PSWS' + ) switch ($ResourceState) { 'Present' { + It 'Should create a web.config file at the web site root' { + Test-Path -Path (Join-Path -Path $ConfigurationData.AllNodes.PhysicalPath -ChildPath 'web.config') | Should -Be $true + } + It ("Should exist a WebSite called $WebsiteName") { Get-WebSite -Name $WebsiteName | Should Not Be $null } @@ -132,6 +193,16 @@ function Test-DSCPullServer $website | Should Not Be $null $website.state | Should BeExactly $WebsiteState } + + It "IIS Application pool $ApplicationPoolName should exist" { + Test-IISApplicationPool -ApplicationPoolName $ApplicationPoolName -ResourceState 'Present' | Should -Be $true + } + + It "WebSite $WebsiteName should be bound to IIS applicaiton pool $ApplicationPoolName" { + $website = Get-WebSite -Name $WebsiteName + $website | Should Not Be $null + $website.applicationPool | Should BeExactly $ApplicationPoolName + } } 'Absent' { @@ -149,7 +220,7 @@ function Test-DSCPullServer .PARAMETER RuleName name of the firewall rule - .PARAMETER State + .PARAMETER ResourceState state of the rule #> function Test-DSCPullServerFirewallRule @@ -165,18 +236,18 @@ function Test-DSCPullServerFirewallRule [Parameter(Mandatory = $true)] [ValidateSet('Present', 'Absent')] [System.String] - $State + $ResourceState ) - Write-Verbose -Message "Test-DSCPullServerFirewallRule $RuleName for state $State." + Write-Verbose -Message "Test-DSCPullServerFirewallRule $RuleName for state $ResourceState." $expectedRuleCount = 0 - if ('Present' -eq $State) + if ('Present' -eq $ResourceState) { $expectedRuleCount = 1 } - It ("Should $(if ('Present' -eq $State) { '' } else { 'not ' })create a firewall rule $RuleName for the chosen port") { + It ("Should $(if ('Present' -eq $ResourceState) { '' } else { 'not ' })create a firewall rule $RuleName for the chosen port") { $ruleCnt = (Get-NetFirewallRule | Where-Object -FilterScript { $_.DisplayName -eq $RuleName #'DSCPullServer_IIS_Port' } | Measure-Object).Count @@ -211,7 +282,6 @@ try Start-Service -Name w3svc -ErrorAction Stop #region Integration Tests - $configurationFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dcsResourceName).config.ps1" . $configurationFile Describe "$($script:dcsResourceName)_Integration" { @@ -236,8 +306,25 @@ try Invoke-CommonResourceTesting -ConfigurationName $configurationName Test-DSCPullServer -WebsiteName 'PSDSCPullServer' -ResourceState 'Present' -WebsiteState 'Started' - Test-DSCPullServerFirewallRule -RuleName 'DSCPullServer_IIS_Port' -State 'Present' + Test-DSCPullServerFirewallRule -RuleName 'DSCPullServer_IIS_Port' -ResourceState 'Present' + } + } + + Context 'Verify clean removal' { + + BeforeAll { + Invoke-CommonResourceTesting -ConfigurationName $ensureAbsentConfigurationName } + + Invoke-CommonResourceTesting -ConfigurationName 'MSFT_xDSCWebService_PullTestWithSecurityBestPractices_Config' + + Test-DSCPullServer -WebsiteName 'PSDSCPullServer' -ResourceState 'Present' -WebsiteState 'Started' + Test-DSCPullServerFirewallRule -RuleName 'DSCPullServer_IIS_Port' -ResourceState 'Present' + + Invoke-CommonResourceTesting -ConfigurationName $ensureAbsentConfigurationName + + Test-DSCPullServer -WebsiteName 'PSDSCPullServer' -ResourceState 'Absent' -WebsiteState 'Absent' + Test-DSCPullServerFirewallRule -RuleName 'DSCPullServer_IIS_Port' -ResourceState 'Absent' } Context 'No firewall configuration' { @@ -254,7 +341,45 @@ try Invoke-CommonResourceTesting -ConfigurationName $configurationName Test-DSCPullServer -WebsiteName 'PSDSCPullServer' -ResourceState 'Present' -WebsiteState 'Started' - Test-DSCPullServerFirewallRule -RuleName 'DSCPullServer_IIS_Port' -State 'Absent' + Test-DSCPullServerFirewallRule -RuleName 'DSCPullServer_IIS_Port' -ResourceState 'Absent' + } + + Context 'Separate firewall role definition' { + + BeforeAll { + Invoke-CommonResourceTesting -ConfigurationName $ensureAbsentConfigurationName + } + + Invoke-CommonResourceTesting -ConfigurationName 'MSFT_xDSCWebService_PullTestWithSeparateFirewallRule_Config' + Test-DSCPullServer -WebsiteName 'PSDSCPullServer' -ResourceState 'Present' -WebsiteState 'Started' + Test-DSCPullServerFirewallRule -RuleName 'DSCPullServer_IIS_Port' -ResourceState 'Absent' + Test-DSCPullServerFirewallRule -RuleName 'DSC PullServer 8080' -ResourceState 'Present' + + + Invoke-CommonResourceTesting -ConfigurationName $ensureAbsentConfigurationName + Test-DSCPullServer -WebsiteName 'PSDSCPullServer' -ResourceState 'Absent' -WebsiteState 'Absent' + Test-DSCPullServerFirewallRule -RuleName 'DSCPullServer_IIS_Port' -ResourceState 'Absent' + Test-DSCPullServerFirewallRule -RuleName 'DSC PullServer 8080' -ResourceState 'Present' + } + + Context 'Separate IIS application pool definition' { + + BeforeAll { + Invoke-CommonResourceTesting -ConfigurationName $ensureAbsentConfigurationName + } + + Invoke-CommonResourceTesting -ConfigurationName 'MSFT_xDSCWebService_PullTestSeparateAppPool_Config' + Test-DSCPullServer -WebsiteName 'PSDSCPullServer' -ResourceState 'Present' -WebsiteState 'Started' -ApplicationPoolName 'PSDSCPullServer_PSDSCPullServer' + Test-DSCPullServerFirewallRule -RuleName 'DSCPullServer_IIS_Port' -ResourceState 'Absent' + + Invoke-CommonResourceTesting -ConfigurationName $ensureAbsentConfigurationName + Test-DSCPullServer -WebsiteName 'PSDSCPullServer' -ResourceState 'Absent' -WebsiteState 'Absent' + Test-DSCPullServerFirewallRule -RuleName 'DSCPullServer_IIS_Port' -ResourceState 'Absent' + + It "Separately created IIS Application pool should still exist after cleanup" { + Test-IISApplicationPool -ApplicationPoolName 'PSDSCPullServer_PSDSCPullServer' -ResourceState 'Present' | Should -Be $true + } + } } #endregion diff --git a/Tests/Integration/MSFT_xDSCWebService.config.ps1 b/Tests/Integration/MSFT_xDSCWebService.config.ps1 index 5e9f78451..ea7f2ff44 100644 --- a/Tests/Integration/MSFT_xDSCWebService.config.ps1 +++ b/Tests/Integration/MSFT_xDSCWebService.config.ps1 @@ -142,3 +142,92 @@ Configuration MSFT_xDSCWebService_PullTestWithoutFirewall_Config } } } + +<# + .SYNOPSIS + Sets up a DSC pull server with a separate firewall rule definition +#> +Configuration MSFT_xDSCWebService_PullTestWithSeparateFirewallRule_Config +{ + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + Import-DscResource -ModuleName 'NetworkingDsc' + + node $AllNodes.NodeName + { + + xDSCWebService Integration_Test + { + Ensure = 'Present' + AcceptSelfSignedCertificates = $true + CertificateThumbPrint = $Node.CertificateThumbprint + ConfigurationPath = $Node.ConfigurationPath + Enable32BitAppOnWin64 = $false + EndpointName = $Node.EndpointName + ModulePath = $Node.ModulePath + Port = $Node.Port + PhysicalPath = $Node.PhysicalPath + RegistrationKeyPath = $Node.RegistrationKeyPath + State = 'Started' + UseSecurityBestPractices = $true + ConfigureFirewall = $false + } + + Firewall PSDSCPullServerRule + { + Ensure = 'Present' + Name = "DSC_PullServer_$($Node.Port)" + DisplayName = "DSC PullServer $($Node.Port)" + Group = 'DSC PullServer' + Enabled = $true + Action = 'Allow' + Direction = 'InBound' + LocalPort = $Node.Port + Protocol = 'TCP' + DependsOn = '[xDscWebService]Integration_Test' + } + + } +} + +<# + .SYNOPSIS + Sets up a DSC pull server with an separately defined application pool +#> +Configuration MSFT_xDSCWebService_PullTestSeparateAppPool_Config +{ + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + Import-DscResource -ModuleName 'xWebAdministration' + + node $AllNodes.NodeName + { + + $ApplicationPool_Name = "PSDSCPullServer_$($Node.EndpointName)" + + xWebAppPool PSDSCPullServerPool + { + Ensure = 'Present' + Name = $ApplicationPool_Name + IdentityType = 'NetworkService' + } + + xDSCWebService Integration_Test + { + Ensure = 'Present' + AcceptSelfSignedCertificates = $true + CertificateThumbPrint = $Node.CertificateThumbprint + ConfigurationPath = $Node.ConfigurationPath + Enable32BitAppOnWin64 = $false + ApplicationPoolName = $ApplicationPool_Name + EndpointName = $Node.EndpointName + ModulePath = $Node.ModulePath + Port = $Node.Port + PhysicalPath = $Node.PhysicalPath + RegistrationKeyPath = $Node.RegistrationKeyPath + State = 'Started' + UseSecurityBestPractices = $true + ConfigureFirewall = $false + DependsOn = '[xWebAppPool]PSDSCPullServerPool' + } + + } +} diff --git a/Tests/Unit/MSFT_xDSCWebService.Tests.ps1 b/Tests/Unit/MSFT_xDSCWebService.Tests.ps1 index e99e0ebed..0eea716b6 100644 --- a/Tests/Unit/MSFT_xDSCWebService.Tests.ps1 +++ b/Tests/Unit/MSFT_xDSCWebService.Tests.ps1 @@ -39,6 +39,7 @@ try CertificateThumbPrint = 'AllowUnencryptedTraffic' EndpointName = 'PesterTestSite' UseSecurityBestPractices = $false + ConfigureFirewall = $false } $serviceData = @{ @@ -52,21 +53,27 @@ try } $websiteDataHTTP = [System.Management.Automation.PSObject] @{ - bindings = [System.Management.Automation.PSObject] @{ + bindings = [System.Management.Automation.PSObject] @{ collection = @( @{ protocol = 'http' bindingInformation = '*:8080:' certificateHash = '' + }, + @{ + protocol = 'http' + bindingInformation = '*:8090:' + certificateHash = '' } ) } - physicalPath = 'TestDrive:\inetpub\PesterTestSite' - state = 'Started' + physicalPath = 'TestDrive:\inetpub\PesterTestSite' + state = 'Started' + applicationPool = 'PSWS' } $websiteDataHTTPS = [System.Management.Automation.PSObject] @{ - bindings = [System.Management.Automation.PSObject] @{ + bindings = [System.Management.Automation.PSObject] @{ collection = @( @{ protocol = 'https' @@ -75,8 +82,9 @@ try } ) } - physicalPath = 'TestDrive:\inetpub\PesterTestSite' - state = 'Started' + physicalPath = 'TestDrive:\inetpub\PesterTestSite' + state = 'Started' + applicationPool = 'PSWS' } $certificateData = @( @@ -149,7 +157,6 @@ try <# Create dummy functions so that Pester is able to mock them #> function Get-Website {} function Get-WebBinding {} - function Stop-Website {} $webConfigPath = 'TestDrive:\inetpub\PesterTestSite\Web.config' @@ -157,7 +164,7 @@ try $null = New-Item -Path $webConfigPath -Value $webConfig Context -Name 'DSC Web Service is not installed' -Fixture { - Mock -CommandName Get-WebSite -MockWith {} + Mock -CommandName Get-WebSite $script:result = $null @@ -173,8 +180,8 @@ try } #region Mocks - Mock -CommandName Get-WebSite -MockWith {return $websiteDataHTTP} - Mock -CommandName Get-WebBinding -MockWith {return @{CertificateHash = $websiteDataHTTPS.bindings.collection[0].certificateHash}} + Mock -CommandName Get-WebSite -MockWith { return $websiteDataHTTP } + Mock -CommandName Get-WebBinding -MockWith { return @{ CertificateHash = $websiteDataHTTPS.bindings.collection[0].certificateHash } } Mock -CommandName Get-ChildItem -ParameterFilter {$Path -eq $websiteDataHTTP.physicalPath -and $Filter -eq '*.svc'} -MockWith {return @{Name = $serviceData.ServiceName}} Mock -CommandName Get-WebConfigAppSetting -ParameterFilter {$AppSettingName -eq 'ModulePath'} -MockWith {return $serviceData.ModulePath} Mock -CommandName Get-WebConfigAppSetting -ParameterFilter {$AppSettingName -eq 'ConfigurationPath'} -MockWith {return $serviceData.ConfigurationPath} @@ -464,6 +471,12 @@ try function Get-Website {} function Get-WebBinding {} function Stop-Website {} + function New-WebAppPool {} + function Remove-WebAppPool {} + function New-WebSite {} + function Start-Website {} + function Get-WebConfigurationProperty {} + function Remove-Website {} #region Mocks Mock -CommandName Get-Command -ParameterFilter {$Name -eq '.\appcmd.exe'} -MockWith { @@ -489,37 +502,35 @@ try } } Mock -CommandName Get-OSVersion -MockWith {@{Major = 6; Minor = 3}} - Mock -CommandName Get-Website #endregion Context -Name 'DSC Service is not installed and Ensure is Absent' -Fixture { #region Mocks Mock -CommandName Test-Path -ParameterFilter { $LiteralPath -like "IIS:\Sites\*" } -MockWith { $false } Mock -CommandName Remove-PSWSEndpoint + Mock -CommandName Remove-PullServerFirewallConfiguration #endregion It 'Should call expected mocks' { Set-TargetResource @testParameters -Ensure Absent - Assert-MockCalled -Exactly -Times 0 -CommandName Get-Website Assert-MockCalled -Exactly -Times 1 -CommandName Get-OSVersion Assert-MockCalled -Exactly -Times 1 -CommandName Test-Path Assert-MockCalled -Exactly -Times 0 -CommandName Remove-PSWSEndpoint Assert-MockCalled -Exactly -Times 0 -CommandName Get-Command + Assert-MockCalled -Exactly -Times 0 -CommandName Remove-PullServerFirewallConfiguration } } Context -Name 'DSC Service is installed and Ensure is Absent' -Fixture { #region Mocks Mock -CommandName Test-Path -ParameterFilter { $LiteralPath -like "IIS:\Sites\*" } -MockWith { $LiteralPath -eq "IIS:\Sites\$($testParameters.EndpointName)" } - Mock -CommandName Get-Website -MockWith { return $websiteDataHTTP } Mock -CommandName Remove-PSWSEndpoint #endregion It 'Should call expected mocks' { Set-TargetResource @testParameters -Ensure Absent - Assert-MockCalled -Exactly -Times 0 -CommandName Get-Website Assert-MockCalled -Exactly -Times 1 -CommandName Get-OSVersion Assert-MockCalled -Exactly -Times 0 -CommandName Get-Command Assert-MockCalled -Exactly -Times 1 -CommandName Test-Path @@ -527,10 +538,9 @@ try } } - #region Mocks - Mock -CommandName Get-Culture -MockWith {@{TwoLetterISOLanguageName = 'en'}} - Mock -CommandName Test-Path -MockWith {$true} - Mock -CommandName New-PSWSEndpoint + #region MSFT_xDSCWebService Mocks + Mock -CommandName Get-Culture -MockWith { @{TwoLetterISOLanguageName = 'en'} } + Mock -CommandName Test-Path -MockWith { $true } Mock -CommandName Update-LocationTagInApplicationHostConfigForAuthentication Mock -CommandName Set-AppSettingsInWebconfig Mock -CommandName Set-BindingRedirectSettingInWebConfig @@ -538,6 +548,76 @@ try Mock -CommandName Test-FilesDiffer -MockWith { $false } #endregion + #region PSWSIISEndpoint Mocks + Mock -CommandName Get-WebConfigurationProperty -ModuleName PSWSIISEndpoint + Mock -CommandName Test-Path -MockWith { $true } -ModuleName PSWSIISEndpoint + Mock -CommandName Test-IISInstall -ModuleName PSWSIISEndpoint + Mock -CommandName Remove-WebAppPool -ModuleName PSWSIISEndpoint + Mock -CommandName Remove-Item -ModuleName PSWSIISEndpoint + Mock -CommandName Copy-PSWSConfigurationToIISEndpointFolder -ModuleName PSWSIISEndpoint + Mock -CommandName New-WebAppPool -ModuleName PSWSIISEndpoint + Mock -CommandName Set-Item -ModuleName PSWSIISEndpoint + Mock -CommandName Get-Item -ParameterFilter { $Path -like 'IIS:\AppPools*' } -MockWith { + [PSCustomObject]@{ + name = Split-Path -Path $Path -Leaf + managedRuntimeVersion = 'v4.0' + enable32BitAppOnWin64 = $false + processModel = [PSCustomObject]@{ + identityType = 4 + } + } + } -ModuleName PSWSIISEndpoint + Mock -CommandName Get-ChildItem -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My\' } -MockWith { + ##### + # we cannot use the existing certificate definitions from $certificateData because the + # mock runs in a different module and thus the variable does not exist + ##### + [System.Management.Automation.PSObject] @{ + Thumbprint = 'AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTT' + Subject = 'PesterTestDuplicateCertificate' + Extensions = [System.Array] @( + [System.Management.Automation.PSObject] @{ + Oid = [System.Management.Automation.PSObject] @{ + FriendlyName = 'Certificate Template Name' + Value = '1.3.6.1.4.1.311.20.2' + } + } + [System.Management.Automation.PSObject] @{} + ) + NotAfter = Get-Date + } + } -ModuleName PSWSIISEndpoint + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'CERT:\LocalMachine\MY\AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTT' } -MockWith { + ##### + # we cannot use the existing certificate definitions from $certificateData because the + # mock runs in a different module and thus the variable does not exist + ##### + [System.Management.Automation.PSObject] @{ + Thumbprint = 'AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTT' + Subject = 'PesterTestDuplicateCertificate' + Extensions = [System.Array] @( + [System.Management.Automation.PSObject] @{ + Oid = [System.Management.Automation.PSObject] @{ + FriendlyName = 'Certificate Template Name' + Value = '1.3.6.1.4.1.311.20.2' + } + } + [System.Management.Automation.PSObject] @{} + ) + NotAfter = Get-Date + } + } -ModuleName PSWSIISEndpoint + Mock -CommandName New-WebSite -ModuleName PSWSIISEndpoint + Mock -CommandName New-SiteID -ModuleName PSWSIISEndpoint -MockWith { + Get-Random -Maximum 10000 -Minimum 1 + } + Mock -CommandName New-Item -ParameterFilter { $Path -like 'IIS:*' } -ModuleName PSWSIISEndpoint + Mock -CommandName Remove-Item -ModuleName PSWSIISEndpoint + Mock -CommandName Get-WebBinding -ModuleName PSWSIISEndpoint + Mock -CommandName Remove-Website -ModuleName PSWSIISEndpoint + Mock -CommandName Start-Website -ModuleName PSWSIISEndpoint + #endregion + Context -Name 'Ensure is Present' -Fixture { $setTargetPaths = @{ DatabasePath = 'TestDrive:\Database' @@ -547,14 +627,17 @@ try } It 'Should call expected mocks' { + Mock -CommandName Get-Website -ModuleName PSWSIISEndpoint + Mock -CommandName Add-PullServerFirewallConfiguration + Set-TargetResource @testParameters @setTargetPaths -Ensure Present Assert-MockCalled -Exactly -Times 3 -CommandName Get-Command Assert-MockCalled -Exactly -Times 1 -CommandName Get-Culture - Assert-MockCalled -Exactly -Times 0 -CommandName Get-Website + Assert-MockCalled -Exactly -Times 2 -CommandName Get-Website -ModuleName PSWSIISEndpoint Assert-MockCalled -Exactly -Times 2 -CommandName Test-Path Assert-MockCalled -Exactly -Times 1 -CommandName Get-OSVersion - Assert-MockCalled -Exactly -Times 1 -CommandName New-PSWSEndpoint + Assert-MockCalled -Exactly -Times 0 -CommandName Add-PullServerFirewallConfiguration Assert-MockCalled -Exactly -Times 3 -CommandName Update-LocationTagInApplicationHostConfigForAuthentication Assert-MockCalled -Exactly -Times 5 -CommandName Set-AppSettingsInWebconfig Assert-MockCalled -Exactly -Times 1 -CommandName Set-BindingRedirectSettingInWebConfig @@ -595,14 +678,22 @@ try } It 'Should call expected mocks' { + + Mock -CommandName Get-Website -MockWith { + [PSCustomObject]@{ + Name = $Name + State = 'Stopped' + applicationPool = 'PSWS' + physicalPath = 'TestDrive:\inetpub\PesterTestSite' + } + } -ModuleName PSWSIISEndpoint + Set-TargetResource @testParameters @setTargetPaths -Ensure Present Assert-MockCalled -Exactly -Times 3 -CommandName Get-Command Assert-MockCalled -Exactly -Times 1 -CommandName Get-Culture - Assert-MockCalled -Exactly -Times 0 -CommandName Get-Website Assert-MockCalled -Exactly -Times 2 -CommandName Test-Path Assert-MockCalled -Exactly -Times 1 -CommandName Get-OSVersion - Assert-MockCalled -Exactly -Times 1 -CommandName New-PSWSEndpoint Assert-MockCalled -Exactly -Times 3 -CommandName Update-LocationTagInApplicationHostConfigForAuthentication Assert-MockCalled -Exactly -Times 5 -CommandName Set-AppSettingsInWebconfig Assert-MockCalled -Exactly -Times 0 -CommandName Set-BindingRedirectSettingInWebConfig @@ -624,14 +715,22 @@ try } It 'Should call expected mocks' { + Mock -CommandName Get-Website -MockWith { + [PSCustomObject]@{ + Name = $Name + State = 'Stopped' + applicationPool = 'PSWS' + physicalPath = 'TestDrive:\inetpub\PesterTestSite' + } + } -ModuleName PSWSIISEndpoint + Set-TargetResource @testParameters @setTargetPaths -Ensure Present Assert-MockCalled -Exactly -Times 3 -CommandName Get-Command Assert-MockCalled -Exactly -Times 1 -CommandName Get-Culture - Assert-MockCalled -Exactly -Times 0 -CommandName Get-Website + Assert-MockCalled -Exactly -Times 2 -CommandName Get-Website -ModuleName PSWSIISEndpoint Assert-MockCalled -Exactly -Times 2 -CommandName Test-Path Assert-MockCalled -Exactly -Times 1 -CommandName Get-OSVersion - Assert-MockCalled -Exactly -Times 1 -CommandName New-PSWSEndpoint Assert-MockCalled -Exactly -Times 3 -CommandName Update-LocationTagInApplicationHostConfigForAuthentication Assert-MockCalled -Exactly -Times 5 -CommandName Set-AppSettingsInWebconfig Assert-MockCalled -Exactly -Times 0 -CommandName Set-BindingRedirectSettingInWebConfig @@ -648,14 +747,22 @@ try } It 'Should call expected mocks' { + Mock -CommandName Get-Website -MockWith { + [PSCustomObject]@{ + Name = $Name + State = 'Stopped' + applicationPool = 'PSWS' + physicalPath = 'TestDrive:\inetpub\PesterTestSite' + } + } -ModuleName PSWSIISEndpoint + Set-TargetResource @testParameters @setTargetPaths -Ensure Present -Enable32BitAppOnWin64 $true Assert-MockCalled -Exactly -Times 3 -CommandName Get-Command Assert-MockCalled -Exactly -Times 1 -CommandName Get-Culture - Assert-MockCalled -Exactly -Times 0 -CommandName Get-Website + Assert-MockCalled -Exactly -Times 2 -CommandName Get-Website -ModuleName PSWSIISEndpoint Assert-MockCalled -Exactly -Times 2 -CommandName Test-Path Assert-MockCalled -Exactly -Times 1 -CommandName Get-OSVersion - Assert-MockCalled -Exactly -Times 1 -CommandName New-PSWSEndpoint Assert-MockCalled -Exactly -Times 3 -CommandName Update-LocationTagInApplicationHostConfigForAuthentication Assert-MockCalled -Exactly -Times 5 -CommandName Set-AppSettingsInWebconfig Assert-MockCalled -Exactly -Times 1 -CommandName Set-BindingRedirectSettingInWebConfig @@ -673,14 +780,22 @@ try It 'Should call expected mocks' { + Mock -CommandName Get-Website -MockWith { + [PSCustomObject]@{ + Name = $Name + State = 'Stopped' + applicationPool = 'PSWS' + physicalPath = 'TestDrive:\inetpub\PesterTestSite' + } + } -ModuleName PSWSIISEndpoint + Set-TargetResource @testParameters @setTargetPaths -Ensure Present -AcceptSelfSignedCertificates $false Assert-MockCalled -Exactly -Times 1 -CommandName Get-Command Assert-MockCalled -Exactly -Times 1 -CommandName Get-Culture - Assert-MockCalled -Exactly -Times 0 -CommandName Get-Website + Assert-MockCalled -Exactly -Times 2 -CommandName Get-Website -ModuleName PSWSIISEndpoint Assert-MockCalled -Exactly -Times 1 -CommandName Test-Path Assert-MockCalled -Exactly -Times 1 -CommandName Get-OSVersion - Assert-MockCalled -Exactly -Times 1 -CommandName New-PSWSEndpoint Assert-MockCalled -Exactly -Times 3 -CommandName Update-LocationTagInApplicationHostConfigForAuthentication Assert-MockCalled -Exactly -Times 5 -CommandName Set-AppSettingsInWebconfig Assert-MockCalled -Exactly -Times 1 -CommandName Set-BindingRedirectSettingInWebConfig @@ -714,6 +829,15 @@ try } It 'Should call expected mocks' { + Mock -CommandName Get-Website -MockWith { + [PSCustomObject]@{ + Name = $Name + State = 'Stopped' + applicationPool = 'PSWS' + physicalPath = 'TestDrive:\inetpub\PesterTestSite' + } + } -ModuleName PSWSIISEndpoint + Set-TargetResource @altTestParameters @setTargetPaths -Ensure Present -CertificateSubject 'PesterTestCertificate' Assert-MockCalled -Exactly -Times 1 -CommandName Find-CertificateThumbprintWithSubjectAndTemplateName @@ -737,6 +861,15 @@ try } It 'Should not throw an error' { + Mock -CommandName Get-Website -MockWith { + [PSCustomObject]@{ + Name = $Name + State = 'Stopped' + applicationPool = 'PSWS' + physicalPath = 'TestDrive:\inetpub\PesterTestSite' + } + } -ModuleName PSWSIISEndpoint + {Set-TargetResource @altTestParameters @setTargetPaths -Ensure Present} | Should -Not -throw } @@ -754,6 +887,175 @@ try {Set-TargetResource @altTestParameters} | Should -Throw } } + + Context -Name 'Verify Firewall handling' -Fixture { + + $setTargetPaths = @{ + DatabasePath = 'TestDrive:\Database' + ConfigurationPath = 'TestDrive:\Configuration' + ModulePath = 'TestDrive:\Module' + RegistrationKeyPath = 'TestDrive:\RegistrationKey' + } + + Mock -CommandName Remove-PSWSEndpoint + + It 'Should not create any firewall rules if disabled' { + $altTestParameters = $testParameters.Clone() + $altTestParameters.Ensure = 'Present' + $altTestParameters.ConfigureFirewall = $false + + Mock -CommandName Add-PullServerFirewallConfiguration + Mock -CommandName Get-Website -MockWith { $null } -ModuleName PSWSIISEndpoint + + Set-TargetResource @altTestParameters @setTargetPaths + + Assert-MockCalled -Exactly -Times 0 -CommandName Add-PullServerFirewallConfiguration + } + + It 'Should create firewall rules when enabled' { + $altTestParameters = $testParameters.Clone() + $altTestParameters.Ensure = 'Present' + $altTestParameters.ConfigureFirewall = $true + + Mock -CommandName Add-PullServerFirewallConfiguration + Mock -CommandName Get-Website -MockWith { $null } -ModuleName PSWSIISEndpoint + + Set-TargetResource @altTestParameters @setTargetPaths + Assert-MockCalled -Exactly -Times 1 -CommandName Add-PullServerFirewallConfiguration + } + + It 'Should always delete firewall rules which match the display internal name and port' { + $altTestParameters = $testParameters.Clone() + $altTestParameters.Ensure = 'Absent' + $altTestParameters.ConfigureFirewall = $true + + Mock -CommandName Get-Website -MockWith { + [PSCustomObject]@{ + Name = $Name + State = 'Stopped' + applicationPool = 'PSWS' + physicalPath = 'TestDrive:\inetpub\PesterTestSite' + } + } -ModuleName PSWSIISEndpoint + Mock -CommandName Get-WebBinding -MockWith { + [PSCustomObject]@{ + protocol = 'http' + bindingInformation = '*:8080:' + } + [PSCustomObject]@{ + protocol = 'http' + bindingInformation = '*:8090:' + } + [PSCustomObject]@{ + protocol = 'http' + bindingInformation = 'http://test.local/DSCPullServer:8010:' + } + } + Mock -CommandName Test-PullServerFirewallConfiguration -MockWith { $true } -ModuleName Firewall + Mock -CommandName Get-Command -ParameterFilter { $Name -eq 'Get-NetFirewallRule' } -MockWith { $true } -ModuleName Firewall + Mock -CommandName Get-NetFirewallRule -MockWith { + if ($DisplayName -notlike 'DSCPullServer_IIS_Port*') + { + throw "Invalid DisplayName filter [$DisplayName] for Get-NetFirewallRule" + } + } -ModuleName Firewall + + Set-TargetResource @altTestParameters @setTargetPaths + + Assert-MockCalled -Exactly -Times 3 -CommandName Test-PullServerFirewallConfiguration -ModuleName Firewall + Assert-MockCalled -Exactly -Times 3 -CommandName Get-NetFirewallRule -ModuleName Firewall + } + } + + Context -Name 'Verify Application Pool handling' -Fixture { + + $setTargetPaths = @{ + DatabasePath = 'TestDrive:\Database' + ConfigurationPath = 'TestDrive:\Configuration' + ModulePath = 'TestDrive:\Module' + RegistrationKeyPath = 'TestDrive:\RegistrationKey' + } + + It 'Ensure is Absent - An AppPool still bound by an application should not be deleted' { + $altTestParameters = $testParameters.Clone() + $altTestParameters.Ensure = 'Absent' + + Mock -CommandName Get-Website -MockWith { + [PSCustomObject]@{ + Name = $Name + State = 'Stopped' + applicationPool = 'PSWS' + physicalPath = 'TestDrive:\inetpub\PesterTestSite' + } + } -ModuleName PSWSIISEndpoint + + Mock -CommandName Get-ChildItem ` + -ParameterFilter { $Path -eq 'TestDrive:\inetpub\PesterTestSite' } ` + -ModuleName PSWSIISEndpoint + + Mock -CommandName Get-AppPoolBinding ` + -MockWith { "Default Web Site"} ` + -ModuleName PSWSIISEndpoint + + Set-TargetResource @altTestParameters @setTargetPaths + + Assert-MockCalled -Exactly -Times 0 -CommandName Remove-WebAppPool -ModuleName PSWSIISEndpoint + } + + It 'Ensure is Present - No standard AppPool that does not exist should throw' { + $altTestParameters = $testParameters.Clone() + $altTestParameters.Ensure = 'Present' + $altTestParameters.ApplicationPoolName = 'NonExistingAppPool' + + Mock -CommandName Test-Path -ParameterFilter { $Path -eq 'IIS:\AppPools\NonExistingAppPool' } -MockWith { + $false + } + + { Set-TargetResource @altTestParameters @setTargetPaths } | Should -Throw + Assert-MockCalled -Exactly -Times 1 -CommandName Test-Path -Scope It + } + + It 'Ensure is Present - No standard AppPool will be created if an external AppPool is specified' { + $altTestParameters = $testParameters.Clone() + $altTestParameters.Ensure = 'Present' + $altTestParameters.ApplicationPoolName = 'PullServer AppPool' + + Mock -CommandName Test-Path -ParameterFilter { $Path -eq 'IIS:\AppPools\PullServer AppPool' } -MockWith { + $true + } + Mock -CommandName New-WebAppPool -ModuleName PSWSIISEndpoint + + Set-TargetResource @altTestParameters @setTargetPaths + + Assert-MockCalled -Exactly -Times 0 -CommandName New-WebAppPool -ModuleName PSWSIISEndpoint + } + + It 'Ensure is Absent - An externally defined AppPool should not be deleted' { + $altTestParameters = $testParameters.Clone() + $altTestParameters.Ensure = 'Absent' + $altTestParameters.ApplicationPoolName = 'PullServer AppPool' + + Mock -CommandName Get-Website -MockWith { + [PSCustomObject]@{ + Name = $Name + State = 'Stopped' + applicationPool = 'PullServer AppPool' + physicalPath = 'TestDrive:\inetpub\PesterTestSite' + } + } -ModuleName PSWSIISEndpoint + Mock -CommandName Test-Path -ParameterFilter { $Path -eq 'IIS:\AppPools\PullServer AppPool' } -MockWith { + $true + } -ModuleName PSWSIISEndpoint + Mock -CommandName Get-AppPoolBinding -MockWith { $null } -ModuleName PSWSIISEndpoint + Mock -CommandName Remove-WebAppPool -ModuleName PSWSIISEndpoint + + Set-TargetResource @altTestParameters @setTargetPaths + + Assert-MockCalled -Exactly -Times 0 -CommandName Test-Path -ModuleName PSWSIISEndpoint -ParameterFilter { $Path -eq 'IIS:\AppPools\PullServer AppPool' } -Scope It + Assert-MockCalled -Exactly -Times 0 -CommandName Get-AppPoolBinding -ModuleName PSWSIISEndpoint -Scope It + Assert-MockCalled -Exactly -Times 0 -CommandName Remove-WebAppPool -ModuleName PSWSIISEndpoint -Scope It + } + } } Describe -Name "$dscResourceName\Test-TargetResource" -Fixture { @@ -778,7 +1080,7 @@ try #endregion Context -Name 'DSC Service is not installed' -Fixture { - Mock -CommandName Get-Website + #Mock -CommandName Get-Website It 'Should return $true when Ensure is Absent' { Test-TargetResource @testParameters -Ensure Absent | Should -Be $true @@ -790,7 +1092,7 @@ try Context -Name 'DSC Web Service is installed as HTTP' -Fixture { Mock -CommandName Get-Website -MockWith {$WebsiteDataHTTP} - Mock -CommandName Test-PullServerFirewallConfiguration -MockWith { $true } + Mock -CommandName Test-PullServerFirewallConfiguration -MockWith { $false } It 'Should return $false when Ensure is Absent' { Test-TargetResource @testParameters -Ensure Absent | Should -Be $false @@ -965,7 +1267,7 @@ try Context -Name 'DSC Web Service is installed as HTTPS' -Fixture { #region Mocks Mock -CommandName Get-Website -MockWith {$websiteDataHTTPS} - Mock -CommandName Test-PullServerFirewallConfiguration -MockWith { $true } + Mock -CommandName Test-PullServerFirewallConfiguration -MockWith { $false } #endregion It 'Should return $false if Certificate Thumbprint is set to AllowUnencryptedTraffic' { @@ -1125,7 +1427,6 @@ try function Get-Website {} function Get-WebBinding {} - function Stop-Website {} $webConfigPath = 'TestDrive:\Web.config' @@ -1148,7 +1449,6 @@ try function Get-Website {} function Get-WebBinding {} - function Stop-Website {} $webConfigPath = 'TestDrive:\Web.config' @@ -1166,7 +1466,6 @@ try function Get-Website {} function Get-WebBinding {} - function Stop-Website {} $appHostConfigSection = [System.Management.Automation.PSObject] @{OverrideMode = ''} @@ -1190,7 +1489,6 @@ try function Get-Website {} function Get-WebBinding {} - function Stop-Website {} Mock -CommandName Get-ChildItem -MockWith {,@($certificateData)}