diff --git a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1 b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1
new file mode 100644
index 000000000..e025ba3b8
--- /dev/null
+++ b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.psm1
@@ -0,0 +1,236 @@
+# Load the Helper Module
+Import-Module -Name "$PSScriptRoot\..\Helper.psm1" -Verbose:$false
+
+# Localized messages
+data LocalizedData
+{
+ # culture="en-US"
+ ConvertFrom-StringData -StringData @'
+ ErrorWebsiteNotRunning = The website '{0}' is not correctly running.
+ VerboseGettingWebsiteBindings = Getting bindings of the website '{0}'.
+ VerboseUrlReturnStatusCode = Url {0} returned status code {1}.
+ VerboseTestTargetFalseStatusCode = Status code of url {0} does not match the desired state.
+ VerboseTestTargetFalseExpectedContent = Content of url {0} does not match the desired state.
+'@
+}
+
+<#
+ .SYNOPSYS
+ This will return a hashtable of results. Once the resource state will nerver be changed to 'absent',
+ this function or will return the resource in the present state or will throw an error.
+#>
+function Get-TargetResource
+{
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")]
+ [CmdletBinding()]
+ [OutputType([Hashtable])]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $WebSiteName,
+
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $RelativeUrl,
+
+ [UInt16[]]
+ $ValidStatusCodes = [int][Net.HttpStatusCode]::OK,
+
+ [String]
+ $ExpectedContent
+ )
+
+ if (Test-WebSiteRunning $WebSiteName $RelativeUrl $ValidStatusCodes $ExpectedContent)
+ {
+ return @{
+ Ensure = 'Present'
+ WebSiteName = $WebSiteName
+ RelativeUrl = $RelativeUrl
+ ValidStatusCodes = $ValidStatusCodes
+ ExpectedContent = $ExpectedContent
+ }
+ }
+ else
+ {
+ $errorMessage = $LocalizedData.ErrorWebsiteNotRunning -f $WebSiteName
+ New-TerminatingError -ErrorId 'WebsiteNotRunning' `
+ -ErrorMessage $errorMessage `
+ -ErrorCategory 'InvalidResult'
+ }
+}
+
+<#
+ .SYNOPSYS
+ Once this resource is only for check the state, this function will always throw an error.
+#>
+function Set-TargetResource
+{
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")]
+ [CmdletBinding()]
+ param
+ (
+ [ValidateSet('Present', 'Absent')]
+ [String]
+ $Ensure = 'Present',
+
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $WebSiteName,
+
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $RelativeUrl,
+
+ [UInt16[]]
+ $ValidStatusCodes = [int][Net.HttpStatusCode]::OK,
+
+ [String]
+ $ExpectedContent
+ )
+
+ $errorMessage = $LocalizedData.ErrorWebsiteNotRunning -f $WebSiteName
+ New-TerminatingError -ErrorId 'WebsiteNotRunning' `
+ -ErrorMessage $errorMessage `
+ -ErrorCategory 'InvalidResult'
+}
+
+<#
+ .SYNOPSYS
+ This tests the desired state. It will return $true if the website is alive or $false if it is not.
+#>
+function Test-TargetResource
+{
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")]
+ [CmdletBinding()]
+ [OutputType([Boolean])]
+ param
+ (
+ [ValidateSet('Present', 'Absent')]
+ [String]
+ $Ensure = 'Present',
+
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $WebSiteName,
+
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $RelativeUrl,
+
+ [UInt16[]]
+ $ValidStatusCodes = [int][Net.HttpStatusCode]::OK,
+
+ [String]
+ $ExpectedContent
+ )
+
+ return Test-WebSiteRunning $WebSiteName $RelativeUrl $ValidStatusCodes $ExpectedContent
+}
+
+#region Helper Functions
+
+function Test-WebSiteRunning
+{
+ [OutputType([Boolean])]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $WebSiteName,
+
+ [Parameter(Mandatory = $true)]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $RelativeUrl,
+
+ [UInt16[]]
+ $ValidStatusCodes,
+
+ [String]
+ $ExpectedContent
+ )
+
+ function Get-UrlStatus
+ {
+ [OutputType([Hashtable])]
+ param ([string] $Url)
+
+ try
+ {
+ $webResponse = Invoke-WebRequest -Uri $Url -UseBasicParsing -DisableKeepAlive
+
+ return @{
+ StatusCode = $webResponse.StatusCode
+ Content = $webResponse.Content -replace "`r`n", "`n"
+ }
+ }
+ catch [Net.WebException]
+ {
+ return @{
+ StatusCode = [int]$_.Exception.Response.StatusCode
+ Content = ''
+ }
+ }
+ }
+
+ Write-Verbose -Message ($LocalizedData.VerboseGettingWebsiteBindings -f $WebSiteName)
+
+ $bindings = Get-WebBinding -Name $WebSiteName
+
+ foreach ($binding in $bindings)
+ {
+ if ($binding.Protocol -in @('http', 'https'))
+ {
+ # Extract IPv6 address
+ if ($binding.bindingInformation -match '^\[(.*?)\]\:(.*?)\:(.*?)$')
+ {
+ $ipAddress = $Matches[1]
+ $port = $Matches[2]
+ $hostName = $Matches[3]
+ }
+ else
+ {
+ $ipAddress, $port, $hostName = $binding.bindingInformation -split '\:'
+ }
+
+ if (-not $hostName)
+ {
+ $hostName = 'localhost'
+ }
+
+ $url = "$($binding.protocol)://$($hostName):$port$RelativeUrl"
+
+ $urlStatus = Get-UrlStatus $url
+
+ Write-Verbose -Message ($LocalizedData.VerboseUrlReturnStatusCode -f $url, $urlStatus.StatusCode)
+
+ if ($ValidStatusCodes -notcontains $urlStatus.StatusCode)
+ {
+ Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseStatusCode -f $url)
+
+ return $false
+ }
+
+ if ($ExpectedContent -and $urlStatus.Content -ne $ExpectedContent)
+ {
+ Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseExpectedContent -f $url)
+
+ return $false
+ }
+ }
+ }
+
+ return $true
+}
+
+#endregion
+
+Export-ModuleMember -Function *-TargetResource
diff --git a/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.schema.mof b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.schema.mof
new file mode 100644
index 000000000..9fcfeeecb
--- /dev/null
+++ b/DSCResources/MSFT_xWebSiteAlive/MSFT_xWebSiteAlive.schema.mof
@@ -0,0 +1,9 @@
+[ClassVersion("1.0.0"), FriendlyName("xWebSiteAlive")]
+class MSFT_xWebSiteAlive : OMI_BaseResource
+{
+ [Write, ValueMap{"Present", "Absent"}, Values{"Present", "Absent"}] String Ensure;
+ [Key] String WebSiteName;
+ [Key] String RelativeUrl;
+ [Write] UInt16 ValidStatusCodes[];
+ [Write] String ExpectedContent;
+};
diff --git a/Examples/Sample_xWebSiteAlive_200ok.ps1 b/Examples/Sample_xWebSiteAlive_200ok.ps1
new file mode 100644
index 000000000..c035b88eb
--- /dev/null
+++ b/Examples/Sample_xWebSiteAlive_200ok.ps1
@@ -0,0 +1,17 @@
+Configuration Sample_xWebSiteAlive_200ok
+{
+ Import-DscResource -Module xWebAdministration
+
+ xWebsite DefaultWebSite
+ {
+ Ensure = 'Present'
+ Name = 'Default Web Site'
+ State = 'Started'
+ }
+
+ xWebSiteAlive WebSiteAlive
+ {
+ WebSiteName = 'Default Web Site'
+ RelativeUrl = '/'
+ }
+}
diff --git a/Examples/Sample_xWebSiteAlive_ExpectedContent.ps1 b/Examples/Sample_xWebSiteAlive_ExpectedContent.ps1
new file mode 100644
index 000000000..aa83ca0eb
--- /dev/null
+++ b/Examples/Sample_xWebSiteAlive_ExpectedContent.ps1
@@ -0,0 +1,52 @@
+Configuration Sample_xWebSiteAlive_ExpectedContent
+{
+ Import-DscResource -Module xWebAdministration
+
+ xWebsite DefaultWebSite
+ {
+ Ensure = 'Present'
+ Name = 'Default Web Site'
+ State = 'Started'
+ }
+
+ xWebSiteAlive WebSiteAlive
+ {
+ WebSiteName = 'Default Web Site'
+ RelativeUrl = '/iisstart.htm'
+ ValidStatusCodes = @(200)
+ ExpectedContent = @'
+
+
+
+
+IIS Windows Server
+
+
+
+
+
+
+
+
+'@
+ }
+}
diff --git a/README.md b/README.md
index 37a0ec3a7..e2c1e8f27 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
[![Build status](https://ci.appveyor.com/api/projects/status/gnsxkjxht31ctan1/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/xwebadministration/branch/master)
-The **xWebAdministration** module contains the **xIISModule**, **xIISLogging**, **xWebAppPool**, **xWebsite**, **xWebApplication**, **xWebVirtualDirectory**, **xSSLSettings** and **xWebConfigKeyValue** DSC resources for creating and configuring various IIS artifacts.
+The **xWebAdministration** module contains the **xIISModule**, **xIISLogging**, **xWebAppPool**, **xWebsite**, **xWebApplication**, **xWebVirtualDirectory**, **xSSLSettings**, **xWebConfigKeyValue** and **xWebSiteAlive** DSC resources for creating and configuring various IIS artifacts.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
@@ -235,10 +235,19 @@ Please check out common DSC Resources [contributing guidelines](https://github.c
* **DefaultApplicationPool**: Name of the default application pool used by websites.
* **AllowSubDirConfig**: Should IIS look for config files in subdirectories, either **true** or **false**
+### xWebSiteAlive
+
+* **WebSiteName**: Name of the website that must be running, such as **Default Web Site**.
+* **RelativeUrl**: A relative url to joint to each website binding.
+* **ValidStatusCodes**: A list of HTTP status codes to be considered successful results. Defaults to **200 OK**.
+* **ExpectedContent**: The content considered to be successful result.
+
## Versions
### Unreleased
+* Added **xWebSiteAlive**.
+
### 1.17.0.0
* Added removal of self signed certificate to the integration tests of **xWebsite**, fixes #276.
diff --git a/Tests/Integration/MSFT_xWebSiteAlive.Integration.Tests.ps1 b/Tests/Integration/MSFT_xWebSiteAlive.Integration.Tests.ps1
new file mode 100644
index 000000000..a77b77e80
--- /dev/null
+++ b/Tests/Integration/MSFT_xWebSiteAlive.Integration.Tests.ps1
@@ -0,0 +1,93 @@
+$script:DSCModuleName = 'xWebAdministration'
+$script:DSCResourceName = 'MSFT_xWebSiteAlive'
+
+#region HEADER
+# Integration Test Template Version: 1.1.1
+[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+ (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+ & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force
+$TestEnvironment = Initialize-TestEnvironment `
+ -DSCModuleName $script:DSCModuleName `
+ -DSCResourceName $script:DSCResourceName `
+ -TestType Integration
+
+#endregion
+
+$tempIisConfigBackupName = "$($script:DSCResourceName)_" + (Get-Date).ToString('yyyyMMdd_HHmmss')
+$tempRequestFilePath = $null
+
+# Using try/finally to always cleanup.
+try
+{
+ $null = Backup-WebConfiguration -Name $tempIisConfigBackupName
+
+ $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:DSCResourceName).config.ps1"
+ . $configFile
+
+ $configData = @{
+ AllNodes = @(
+ @{
+ NodeName = 'localhost'
+ WebSiteName = 'WebsiteForxWebSiteAlive'
+ PhysicalPath = Join-Path $env:SystemDrive 'inetpub\wwwroot\'
+ HTTPPort = 80
+ RequestFileName = 'xWebSiteAliveTest.html'
+ RequestFileContent = @'
+
+
+IIS Windows Server
+
+
+
+
+'@
+ }
+ )
+ }
+
+ New-Website -Name $configData.AllNodes.WebSiteName `
+ -PhysicalPath $configData.AllNodes.PhysicalPath `
+ -Port $configData.AllNodes.HTTPPort `
+ -Force `
+ -ErrorAction Stop
+
+ # Write without a BOM
+ $tempRequestFilePath = Join-Path $configData.AllNodes.PhysicalPath $configData.AllNodes.RequestFileName
+ [IO.File]::WriteAllText($tempRequestFilePath, $configData.AllNodes.RequestFileContent)
+
+ #region Integration Tests
+
+ Describe "$($script:DSCResourceName)_Integration" {
+ #region DEFAULT TESTS
+ It 'Should compile and apply the MOF without throwing' {
+ {
+ & "$($script:DSCResourceName)_Config" -OutputPath $TestDrive -ConfigurationData $configData
+ Start-DscConfiguration -Path $TestDrive `
+ -ComputerName localhost -Wait -Verbose -Force
+ } | Should not throw
+ }
+
+ It 'Should be able to call Get-DscConfiguration without throwing' {
+ { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not throw
+ }
+ #endregion
+ }
+
+ #endregion
+}
+finally
+{
+ #region FOOTER
+ Restore-WebConfiguration -Name $tempIisConfigBackupName
+ Remove-WebConfigurationBackup -Name $tempIisConfigBackupName
+
+ Remove-Item -Path $tempRequestFilePath -Force
+
+ Restore-TestEnvironment -TestEnvironment $TestEnvironment
+ #endregion
+}
diff --git a/Tests/Integration/MSFT_xWebSiteAlive.config.ps1 b/Tests/Integration/MSFT_xWebSiteAlive.config.ps1
new file mode 100644
index 000000000..9e2d322f5
--- /dev/null
+++ b/Tests/Integration/MSFT_xWebSiteAlive.config.ps1
@@ -0,0 +1,13 @@
+Configuration MSFT_xWebSiteAlive_Config {
+ Import-DscResource -ModuleName xWebAdministration
+
+ Node $AllNodes.NodeName {
+ xWebSiteAlive WebSiteAlive
+ {
+ WebSiteName = $Node.WebSiteName
+ RelativeUrl = "/$($Node.RequestFileName)"
+ ValidStatusCodes = 200
+ ExpectedContent = $Node.RequestFileContent
+ }
+ }
+}
diff --git a/Tests/Unit/MSFT_xWebSiteAlive.Tests.ps1 b/Tests/Unit/MSFT_xWebSiteAlive.Tests.ps1
new file mode 100644
index 000000000..f0f601f56
--- /dev/null
+++ b/Tests/Unit/MSFT_xWebSiteAlive.Tests.ps1
@@ -0,0 +1,408 @@
+$script:DSCModuleName = 'xWebAdministration'
+$script:DSCResourceName = 'MSFT_xWebSiteAlive'
+
+#region HEADER
+
+$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+ (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+ & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force
+
+$TestEnvironment = Initialize-TestEnvironment `
+ -DSCModuleName $script:DSCModuleName `
+ -DSCResourceName $script:DSCResourceName `
+ -TestType Unit
+
+#endregion HEADER
+
+# Begin Testing
+try
+{
+ InModuleScope -ModuleName $script:DSCResourceName -ScriptBlock {
+
+ Describe "$script:DSCResourceName\Get-TargetResource" {
+
+ $mockWebBindings = @(
+ @{
+ Protocol = 'http'
+ bindingInformation = '*:80:'
+ }
+ )
+
+ $splat = @{
+ WebSiteName = 'Default Web Site'
+ RelativeUrl = '/test'
+ ValidStatusCodes = 100, 200, 301
+ ExpectedContent = @'
+
+
+IIS Windows Server
+
+
+
+
+'@ -replace "`r`n", "`n" # In the real execution DSC will send `n as line terminators
+ }
+
+ Context 'The website is alive' {
+ $mockUrlResultOk = @{
+ StatusCode = 200
+ Content = @'
+
+
+IIS Windows Server
+
+
+
+
+'@
+ }
+
+ Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings }
+
+ Mock -CommandName 'Invoke-WebRequest' -MockWith { $mockUrlResultOk }
+
+ $result = Get-TargetResource @splat
+
+ It 'Should return Ensure' {
+ $result.Ensure | Should Be 'Present'
+ }
+
+ It 'Should return WebSiteName' {
+ $result.WebSiteName | Should Be $splat.WebSiteName
+ }
+
+ It 'Should return RelativeUrl' {
+ $result.RelativeUrl | Should Be $splat.RelativeUrl
+ }
+
+ It 'Should return ValidStatusCodes' {
+ $result.ValidStatusCodes | Should Be $splat.ValidStatusCodes
+ }
+
+ It 'Should return ExpectedContent' {
+ $result.ExpectedContent | Should Be $splat.ExpectedContent
+ }
+ }
+
+ Context 'The website is not alive' {
+ $mockUrlResultInternalServerError = @{
+ StatusCode = 500
+ Content = ''
+ }
+
+ Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings }
+
+ Mock -CommandName 'Invoke-WebRequest' -MockWith { $mockUrlResultInternalServerError }
+
+ It 'Should throw an error' {
+ { Get-TargetResource @splat } | Should Throw
+ }
+ }
+ }
+
+ Describe "$script:DSCResourceName\Set-TargetResource" {
+ It 'Should throw an error' {
+ $splat = @{
+ WebSiteName = 'Default Web Site'
+ RelativeUrl = '/'
+ }
+
+ { Set-TargetResource @splat } | Should Throw
+ }
+ }
+
+ Describe "$script:DSCResourceName\Test-TargetResource" {
+
+ $mockUrlResultOk = @{
+ StatusCode = 200
+ Content = ''
+ }
+
+ Context 'There are multiple websites' {
+ $mockWebBindings01 = @(
+ @{
+ Protocol = 'http'
+ bindingInformation = '*:2000:'
+ },
+ @{
+ Protocol = 'https'
+ bindingInformation = '*:3000:'
+ }
+ )
+
+ $mockWebBindings02 = @(
+ @{
+ Protocol = 'http'
+ bindingInformation = '*:4000:'
+ },
+ @{
+ Protocol = 'https'
+ bindingInformation = '*:5000:'
+ }
+ )
+
+ $mockWebBindings03 = @(
+ @{
+ Protocol = 'http'
+ bindingInformation = '*:6000:'
+ }
+ )
+
+ $splatWebsite02 = @{
+ WebSiteName = 'WebSite02'
+ RelativeUrl = '/'
+ }
+
+ Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq 'WebSite01' } -MockWith { return $mockWebBindings01 }
+
+ Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splatWebsite02.WebSiteName } -MockWith { return $mockWebBindings02 }
+
+ Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq 'WebSite03' } -MockWith { return $mockWebBindings03 }
+
+ Mock -CommandName 'Invoke-WebRequest' -MockWith { $mockUrlResultOk }
+
+ Test-TargetResource @splatWebsite02
+
+ It 'Should request the urls from the correct website' {
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://localhost:4000/' }
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'https://localhost:5000/' }
+ }
+
+ It 'Should not request the urls from the undesired websites' {
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 0 -ParameterFilter { $Uri -eq 'http://localhost:2000/' }
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 0 -ParameterFilter { $Uri -eq 'https://localhost:3000/' }
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 0 -ParameterFilter { $Uri -eq 'http://localhost:6000/' }
+ }
+ }
+
+ Context 'There are multiple website bindings' {
+ $mockWebBindings = @(
+ @{
+ Protocol = 'http'
+ bindingInformation = '*:80:'
+ },
+ @{
+ Protocol = 'http'
+ bindingInformation = '*:11000:'
+ },
+ @{
+ Protocol = 'http'
+ bindingInformation = '[::1]:12000:'
+ },
+ @{
+ Protocol = 'https'
+ bindingInformation = '[0:0:0:0:0:ffff:d1ad:35a7]:13000:www.domaintest.com'
+ },
+ @{
+ Protocol = 'net.tcp'
+ bindingInformation = '808:*'
+ }
+ )
+
+ $splat = @{
+ WebSiteName = 'Default Web Site'
+ RelativeUrl = '/'
+ }
+
+ Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings }
+
+ Mock -CommandName 'Invoke-WebRequest' -MockWith { $mockUrlResultOk }
+
+ Test-TargetResource @splat
+
+ It 'Should request the correct urls' {
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://localhost:80/' }
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://localhost:11000/' }
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://localhost:12000/' }
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'https://www.domaintest.com:13000/' }
+ }
+
+ It 'Should request only the correct protocols' {
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 0 -ParameterFilter { $Uri.ToString().StartsWith('net.tcp') }
+ }
+ }
+
+ Context 'There are multiple website bindings and a relative URL is passed' {
+ $mockWebBindings = @(
+ @{
+ Protocol = 'http'
+ bindingInformation = '[::1]:11000:'
+ },
+ @{
+ Protocol = 'https'
+ bindingInformation = '[0:0:0:0:0:ffff:d1ad:35a7]:12000:www.domaintest01.com'
+ },
+ @{
+ Protocol = 'net.tcp'
+ bindingInformation = '808:*'
+ },
+ @{
+ Protocol = 'http'
+ bindingInformation = '*:80:www.domaintest02.com'
+ },
+ @{
+ Protocol = 'http'
+ bindingInformation = '*:13000:'
+ }
+ )
+
+ $splat = @{
+ WebSiteName = 'Default Web Site'
+ RelativeUrl = '/relative/path/index.html'
+ }
+
+ Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings }
+
+ Mock -CommandName 'Invoke-WebRequest' -MockWith { $mockUrlResultOk }
+
+ Test-TargetResource @splat
+
+ It 'Should request the correct urls' {
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://localhost:11000/relative/path/index.html' }
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'https://www.domaintest01.com:12000/relative/path/index.html' }
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://www.domaintest02.com:80/relative/path/index.html' }
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 1 -ParameterFilter { $Uri -eq 'http://localhost:13000/relative/path/index.html' }
+
+ }
+
+ It 'Should request only the correct protocols' {
+ Assert-MockCalled -CommandName Invoke-WebRequest -Exactly 0 -ParameterFilter { $Uri.ToString().StartsWith('net.tcp') }
+ }
+ }
+
+ $mockWebBindings = @(
+ @{
+ Protocol = 'http'
+ bindingInformation = '*:21000:'
+ }
+ )
+
+ $splat = @{
+ WebSiteName = 'Default Web Site'
+ RelativeUrl = '/test'
+ ValidStatusCodes = 100, 200, 300
+ }
+
+ Context 'A list of valid status codes was passed and the website is alive' {
+
+ Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings }
+
+ Mock -CommandName 'Invoke-WebRequest' -ParameterFilter { $Uri -eq 'http://localhost:21000/test' } -MockWith { $mockUrlResultOk }
+
+ It 'Should be true' {
+ Test-TargetResource @splat | Should be $true
+ }
+ }
+
+ Context 'A list of valid status codes was passed and the website is not alive' {
+ $mockUrlResultInternalServerError = @{
+ StatusCode = 500
+ Content = ''
+ }
+
+ Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings }
+
+ Mock -CommandName 'Invoke-WebRequest' -ParameterFilter { $Uri -eq 'http://localhost:21000/test' } -MockWith { $mockUrlResultInternalServerError }
+
+ It 'Should be false' {
+ Test-TargetResource @splat | Should be $false
+ }
+ }
+
+ $splat = @{
+ WebSiteName = 'Default Web Site'
+ RelativeUrl = '/test'
+ ValidStatusCodes = 200
+ ExpectedContent = @'
+
+
+IIS Windows Server
+
+
+
+
+'@ -replace "`r`n", "`n" # In the real execution DSC will send `n as line terminators
+ }
+
+ Context 'A expected content was passed and the result match' {
+
+ $mockUrlResultContentMatch = @{
+ StatusCode = 200
+ Content = @'
+
+
+IIS Windows Server
+
+
+
+
+'@
+ }
+
+ Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings }
+
+ Mock -CommandName 'Invoke-WebRequest' -ParameterFilter { $Uri -eq 'http://localhost:21000/test' } -MockWith { $mockUrlResultContentMatch }
+
+ It 'Should be true' {
+ Test-TargetResource @splat | Should be $true
+ }
+ }
+
+ Context 'A expected content was passed and the result does not match' {
+
+ $mockUrlResultContentDontMatch = @{
+ StatusCode = 200
+ Content = @'
+
+
+IIS Windows Server
+
+ diff
+
+
+'@
+ }
+
+ Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings }
+
+ Mock -CommandName 'Invoke-WebRequest' -ParameterFilter { $Uri -eq 'http://localhost:21000/test' } -MockWith { $mockUrlResultContentDontMatch }
+
+ It 'Should be false' {
+ Test-TargetResource @splat | Should be $false
+ }
+ }
+
+ Context 'A expected content was passed and the website is not alive' {
+ $mockUrlResultInternalServerError = @{
+ StatusCode = 500
+ Content = @'
+
+
+IIS Windows Server
+
+
+
+
+'@
+ }
+
+ Mock -CommandName 'Get-WebBinding' -ParameterFilter { $Name -eq $splat.WebSiteName } -MockWith { return $mockWebBindings }
+
+ Mock -CommandName 'Invoke-WebRequest' -ParameterFilter { $Uri -eq 'http://localhost:21000/test' } -MockWith { $mockUrlResultInternalServerError }
+
+ It 'Should be false' {
+ Test-TargetResource @splat | Should be $false
+ }
+ }
+ }
+ }
+}
+finally
+{
+ Restore-TestEnvironment -TestEnvironment $TestEnvironment
+}
diff --git a/appveyor.yml b/appveyor.yml
index b1c94a908..acfa8b768 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -2,6 +2,10 @@
# environment configuration #
#---------------------------------#
version: 1.17.{build}.0
+
+services:
+ - iis
+
install:
- git clone https://github.com/PowerShell/DscResource.Tests
- ps: |