diff --git a/DSCResources/Helper.psm1 b/DSCResources/Helper.psm1 index 220768529..9e9ed782c 100644 --- a/DSCResources/Helper.psm1 +++ b/DSCResources/Helper.psm1 @@ -3,19 +3,42 @@ data LocalizedData { # culture="en-US" ConvertFrom-StringData -StringData @' - ModuleNotFound = Please ensure that the PowerShell module for role {0} is installed. + ModuleNotFound = Please ensure that the PowerShell module for role {0} is installed. + ErrorWebsiteNotFound = The requested website "{0}" cannot be found on the target machine. + ErrorWebsiteBindingUpdateFailure = Failure to successfully update the bindings for website "{0}". Error: "{1}". + ErrorWebsiteBindingInputInvalidation = Desired website bindings are not valid for website "{0}". + ErrorWebsiteCompareFailure = Failure to successfully compare properties for website "{0}". Error: "{1}". + ErrorWebBindingCertificate = Failure to add certificate to web binding. Please make sure that the certificate thumbprint "{0}" is valid. Error: "{1}". + ErrorWebBindingInvalidCertificateSubject = The Subject "{0}" provided is not found on this host in store "{1}" + ErrorWebBindingInvalidIPAddress = Failure to validate the IPAddress property value "{0}". Error: "{1}". + ErrorWebBindingInvalidPort = Failure to validate the Port property value "{0}". The port number must be a positive integer between 1 and 65535. + ErrorWebBindingMissingBindingInformation = The BindingInformation property is required for bindings of type "{0}". + ErrorWebBindingMissingCertificateThumbprint = The CertificateThumbprint property is required for bindings of type "{0}". + ErrorWebBindingMissingSniHostName = The HostName property is required for use with Server Name Indication. + ErrorWebsiteTestAutoStartProviderFailure = Desired AutoStartProvider is not valid due to a conflicting Global Property. Ensure that the serviceAutoStartProvider is a unique key." + VerboseConvertToWebBindingDefaultCertificateStoreName = CertificateStoreName is not specified. The default value "{0}" will be used. + VerboseConvertToWebBindingDefaultPort = Port is not specified. The default "{0}" port "{1}" will be used. + VerboseConvertToWebBindingIgnoreBindingInformation = BindingInformation is ignored for bindings of type "{0}" in case at least one of the following properties is specified: IPAddress, Port, HostName. + VerboseTestBindingInfoInvalidCatch = Unable to validate BindingInfo: "{0}". + VerboseTestBindingInfoSameIPAddressPortHostName = BindingInfo contains multiple items with the same IPAddress, Port, and HostName combination. + VerboseTestBindingInfoSamePortDifferentProtocol = BindingInfo contains items that share the same Port but have different Protocols. + VerboseTestBindingInfoSameProtocolBindingInformation = BindingInfo contains multiple items with the same Protocol and BindingInformation combination. + VerboseUpdateDefaultPageUpdated = Default page for website "{0}" has been updated to "{1}". '@ } <# - .SYNOPSIS + .SYNOPSIS Internal function to throw terminating error with specified - errroCategory, errorId and errorMessage - .PARAMETER ErrorId + errroCategory, errorId and errorMessage. + + .PARAMETER ErrorId Specifies the Id error message. - .PARAMETER ErrorMessage + + .PARAMETER ErrorMessage Specifies full Error Message to be returned. - .PARAMETER ErrorCategory + + .PARAMETER ErrorCategory Specifies Error Category. #> function New-TerminatingError @@ -23,34 +46,40 @@ function New-TerminatingError [CmdletBinding()] param ( - [Parameter(Mandatory)] - [String] $ErrorId, + [Parameter(Mandatory = $true)] + [String] + $ErrorId, - [Parameter(Mandatory)] - [String] $ErrorMessage, + [Parameter(Mandatory = $true)] + [String] + $ErrorMessage, - [Parameter(Mandatory)] - [System.Management.Automation.ErrorCategory] $ErrorCategory + [Parameter(Mandatory = $true)] + [System.Management.Automation.ErrorCategory] + $ErrorCategory ) $exception = New-Object System.InvalidOperationException $ErrorMessage $errorRecord = New-Object System.Management.Automation.ErrorRecord ` - $exception, $ErrorId, $ErrorCategory, $null + $exception, $ErrorId, $ErrorCategory, $null throw $errorRecord } <# .SYNOPSIS - Internal function to assert if the module exists + Internal function to assert if the module exists. + .PARAMETER ModuleName - Module to test + Module to test. #> function Assert-Module { [CmdletBinding()] param ( - [String]$ModuleName = 'WebAdministration' + [Parameter()] + [String] + $ModuleName = 'WebAdministration' ) if(-not(Get-Module -Name $ModuleName -ListAvailable)) @@ -64,39 +93,38 @@ function Assert-Module <# .SYNOPSIS - Locates one or more certificates using the passed certificate selector parameters. + Locates one or more certificates using the passed certificate selector parameters. - If more than one certificate is found matching the selector criteria, they will be - returned in order of descending expiration date. + If more than one certificate is found matching the selector criteria, they will be + returned in order of descending expiration date. .PARAMETER Thumbprint - The thumbprint of the certificate to find. + The thumbprint of the certificate to find. .PARAMETER FriendlyName - The friendly name of the certificate to find. + The friendly name of the certificate to find. .PARAMETER Subject - The subject of the certificate to find. + The subject of the certificate to find. .PARAMETER DNSName - The subject alternative name of the certificate to export must contain these values. + The subject alternative name of the certificate to export must contain these values. .PARAMETER Issuer - The issuer of the certiicate to find. + The issuer of the certiicate to find. .PARAMETER KeyUsage - The key usage of the certificate to find must contain these values. + The key usage of the certificate to find must contain these values. .PARAMETER EnhancedKeyUsage - The enhanced key usage of the certificate to find must contain these values. + The enhanced key usage of the certificate to find must contain these values. .PARAMETER Store - The Windows Certificate Store Name to search for the certificate in. - Defaults to 'My'. + The Windows Certificate Store Name to search for the certificate in. + Defaults to 'My'. .PARAMETER AllowExpired - Allows expired certificates to be returned. - + Allows expired certificates to be returned. #> function Find-Certificate { @@ -239,7 +267,6 @@ function Find-Certificate .PARAMETER ResourcePath The path the resource file is located in. #> - function Get-LocalizedData { [CmdletBinding()] @@ -271,3 +298,1308 @@ function Get-LocalizedData return $localizedData } + +#region Authentication Functions + +<# + .SYNOPSIS + Helper function used to get the authentication properties + for a Website,FTP Site or Application. + + .PARAMETER Site + Specifies the name of the Website. + + .PARAMETER Application + Specifies the name of the Application if IiisType is set to Application. + + .PARAMETER IisType + Specifies type of the object to get authentication properties for. + It could be a Website, FTP Site or Application. +#> +function Get-AuthenticationInfo +{ + [CmdletBinding()] + [OutputType([Microsoft.Management.Infrastructure.CimInstance])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Site, + + [Parameter()] + [String] + $Application, + + [Parameter(Mandatory = $true)] + [ValidateSet('Website','Application','Ftp')] + [String] + $IisType + ) + + $authAvailable = Get-DefaultAuthenticationInfo -IisType $IisType + + $authenticationProperties = @{} + foreach ($type in ($authAvailable.CimInstanceProperties.Name | Sort-Object)) + { + $authenticationProperties[$type] = ` + [Boolean](Test-AuthenticationEnabled @PSBoundParameters -Type $type) + } + + return New-CimInstance -ClassName $authAvailable.CimClass.CimClassName ` + -ClientOnly -Property $authenticationProperties ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' +} + +<# + .SYNOPSIS + Helper function used to build a default CimInstance of AuthenticationInformation + for a Website,FTP Site or Application. + + .PARAMETER IisType + Specifies type of the object to get default authentication properties for. + It could be a Website, FTP Site or Application. +#> +function Get-DefaultAuthenticationInfo +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateSet('Website','Application','Ftp')] + [String] + $IisType + ) + + $cimNamespace = 'root/microsoft/Windows/DesiredStateConfiguration' + switch($IisType) + { + Website + { + New-CimInstance -ClassName MSFT_xWebAuthenticationInformation ` + -ClientOnly -Namespace $cimNamespace ` + -Property @{Anonymous=$false;Basic=$false;Digest=$false;Windows=$false} + } + + Application + { + New-CimInstance -ClassName MSFT_xWebApplicationAuthenticationInformation ` + -ClientOnly -Namespace $cimNamespace ` + -Property @{Anonymous=$false;Basic=$false;Digest=$false;Windows=$false} + } + + Ftp + { + New-CimInstance -ClassName MSFT_FTPAuthenticationInformation ` + -ClientOnly -Namespace $cimNamespace ` + -Property @{Anonymous=$false;Basic=$false} + } + } +} + +<# + .SYNOPSIS + Helper function used to set single authentication property + for a Website,FTP Site or Application. + + .PARAMETER Site + Specifies the name of the Website. + + .PARAMETER Application + Specifies the name of the Application. + + .PARAMETER IisType + Specifies type of the object to set authentication property on. + It could be a Website, FTP Site or Application. + + .PARAMETER Type + Specifies the type of Authentication, + Limited to the set: ('Anonymous','Basic','Digest','Windows'). + + .PARAMETER Enabled + Whether the Authentication is enabled or not. +#> +function Set-Authentication +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Site, + + [Parameter()] + [String] + $Application, + + [Parameter(Mandatory = $true)] + [ValidateSet('Website','Application','Ftp')] + [String] + $IisType, + + [Parameter(Mandatory = $true)] + [ValidateSet('Anonymous','Basic','Digest','Windows')] + [String] + $Type, + + [Parameter()] + [System.Boolean] + $Enabled + ) + + $Location = "${Site}" + + if ($IisType -eq 'Application') + { + $Location = "${Site}/${Application}" + } + + switch ($IisType) + { + 'Ftp' { Set-ItemProperty "IIS:\Sites\$Location" ` + -Name ftpServer.security.authentication.${Type}Authentication.enabled ` + -Value $Enabled + } + default { Set-WebConfigurationProperty ` + -Filter /system.WebServer/security/authentication/${Type}Authentication ` + -Name enabled ` + -Value $Enabled ` + -Location $Location + } + } +} + +<# + .SYNOPSIS + Helper function used to set the authentication properties + for a Website,FTP Site or Application. + + .PARAMETER Site + Specifies the name of the Website. + + .PARAMETER Application + Specifies the name of the Application. + + .PARAMETER IisType + Specifies type of the object to set authentication properties for. + It could be a Website, FTP Site or Application. + + .PARAMETER AuthenticationInfo + A CimInstance of what state the AuthenticationInfo should be. +#> +function Set-AuthenticationInfo +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Site, + + [Parameter()] + [String] + $Application, + + [Parameter(Mandatory = $true)] + [ValidateSet('Website','Application','Ftp')] + [String] + $IisType, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimInstance] + $AuthenticationInfo + ) + + $Properties = ($AuthenticationInfo.CimInstanceProperties | Where-Object {$null -ne $_.Value}).Name | Sort-Object + $PSBoundParameters.Remove('AuthenticationInfo') | Out-Null + + foreach ($type in $Properties) + { + $enabled = ($AuthenticationInfo.CimInstanceProperties[$type].Value -eq $true) + Set-Authentication @PSBoundParameters -Type $type -Enabled $enabled + } +} + +<# + .SYNOPSIS + Helper function used to test authentication property state. + Will return that value which will either [String]True or [String]False + + .PARAMETER Site + Specifies the name of the Website. + + .PARAMETER Application + Specifies the name of the Application. + + .PARAMETER IisType + Specifies type of the object to tests authentication property for. + It could be a Website, FTP Site or Application. + + .PARAMETER Type + Specifies the type of Authentication, + limited to the set: ('Anonymous','Basic','Digest','Windows'). +#> +function Test-AuthenticationEnabled +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Site, + + [Parameter()] + [String] + $Application, + + [Parameter(Mandatory = $true)] + [ValidateSet('Website','Application','Ftp')] + [String] + $IisType, + + [Parameter(Mandatory = $true)] + [ValidateSet('Anonymous','Basic','Digest','Windows')] + [String] + $Type + ) + + $Location = "${Site}" + + if ($IisType -eq 'Application') + { + $Location = "${Site}/${Application}" + } + + $prop = switch ($IisType) + { + 'Ftp' { Get-ItemProperty "IIS:\Sites\$Location" ` + -Name ftpServer.security.authentication.${Type}Authentication.enabled + } + default { Get-WebConfigurationProperty ` + -Filter /system.WebServer/security/authentication/${Type}Authentication ` + -Name enabled ` + -Location $Location + } + } + + return $prop.Value +} + +<# + .SYNOPSIS + Helper function used to test the authentication properties state. + Will return that result which will either [boolean]$True or [boolean]$False for use in + Test-TargetResource. + Uses Test-AuthenticationEnabled to determine this. First incorrect result will break + this function out. + + .PARAMETER Application + Specifies the name of the Website. + + .PARAMETER Name + Specifies the name of the Application. + + .PARAMETER IisType + Specifies type of the object to tests authentication properties for. + It could be a Website, FTP Site or Application. + + .PARAMETER AuthenticationInfo + A CimInstance of what state the AuthenticationInfo should be. +#> +function Test-AuthenticationInfo +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Site, + + [Parameter()] + [String] + $Application, + + [Parameter(Mandatory = $true)] + [ValidateSet('Website','Application','Ftp')] + [String] + $IisType, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimInstance] + $AuthenticationInfo + ) + + $Properties = ($AuthenticationInfo.CimInstanceProperties | Where-Object {$null -ne $_.Value}).Name | Sort-Object + $PSBoundParameters.Remove('AuthenticationInfo') | Out-Null + + foreach ($type in $Properties) + { + $expected = $AuthenticationInfo.CimInstanceProperties[$type].Value + $actual = Test-AuthenticationEnabled @PSBoundParameters -Type $type + + if ($expected -ne $actual) + { + return $false + } + } + + return $true +} + +#endregion + +#region Log functions + +<# + .SYNOPSIS + Helper function used to validate the logflags status. + Returns False if the loglfags do not match and true if they do. + + .PARAMETER LogFlags + Specifies flags to check. + + .PARAMETER Name + Specifies website to check the flags on. + + .PARAMETER FtpSite + Specifies whether current site is FTP site. +#> +function Compare-LogFlags +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [String[]] + [ValidateSet('Date','Time','ClientIP','UserName','SiteName','ComputerName','ServerIP','Method','UriStem','UriQuery','HttpStatus','Win32Status','BytesSent','BytesRecv','TimeTaken','ServerPort','UserAgent','Cookie','Referer','ProtocolVersion','Host','HttpSubStatus')] + $LogFlags, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Name, + + [Parameter()] + [Switch] + $FtpSite + ) + + $CurrentLogFlags = switch($FtpSite.IsPresent) + { + $true { (Get-Website -Name $Name).ftpServer.logFile.logExtFileFlags -split ',' | Sort-Object } + default { (Get-Website -Name $Name).logfile.logExtFileFlags -split ',' | Sort-Object } + } + $ProposedLogFlags = $LogFlags -split ',' | Sort-Object + + if (Compare-Object -ReferenceObject $CurrentLogFlags -DifferenceObject $ProposedLogFlags) + { + return $false + } + + return $true +} + +<# + .SYNOPSIS + Converts IIS custom log field collection to instances of the MSFT_xLogCustomFieldInformation CIM class. +#> +function ConvertTo-CimLogCustomFields +{ + [CmdletBinding()] + [OutputType([Microsoft.Management.Infrastructure.CimInstance[]])] + param + ( + [Parameter(Mandatory = $true)] + [AllowEmptyCollection()] + [AllowNull()] + [Object[]] + $InputObject + ) + + $cimClassName = 'MSFT_xLogCustomFieldInformation' + $cimNamespace = 'root/microsoft/Windows/DesiredStateConfiguration' + $cimCollection = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]' + + foreach ($customField in $InputObject) + { + $cimProperties = @{ + LogFieldName = $customField.LogFieldName + SourceName = $customField.SourceName + SourceType = $customField.SourceType + } + + $cimCollection += (New-CimInstance -ClassName $cimClassName ` + -Namespace $cimNamespace ` + -Property $cimProperties ` + -ClientOnly) + } + + return $cimCollection +} + +#endregion + +#region Autostart functions + +<# + .SYNOPSIS + Helper function used to validate that the AutoStartProviders is unique to other websites. + returns False if the AutoStartProviders exist. + + .PARAMETER ServiceAutoStartProvider + Specifies the name of the AutoStartProviders. + + .PARAMETER ApplicationType + Specifies the name of the Application Type for the AutoStartProvider. + + .NOTES + This tests for the existance of a AutoStartProviders which is globally assigned. + As AutoStartProviders need to be uniquely named it will check for this and error out if + attempting to add a duplicatly named AutoStartProvider. + Name is passed in to bubble to any error messages during the test. +#> +function Confirm-UniqueServiceAutoStartProviders +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $ServiceAutoStartProvider, + + [Parameter(Mandatory = $true)] + [String] + $ApplicationType + ) + + $WebSiteASP = (Get-WebConfiguration ` + -filter /system.applicationHost/serviceAutoStartProviders).Collection + + $ExistingObject = $WebSiteASP | ` + Where-Object -Property Name -eq -Value $serviceAutoStartProvider | ` + Select-Object Name,Type + + $ProposedObject = @(New-Object -TypeName PSObject -Property @{ + name = $ServiceAutoStartProvider + type = $ApplicationType + }) + + if(-not $ExistingObject) + { + return $false + } + + if(-not (Compare-Object -ReferenceObject $ExistingObject ` + -DifferenceObject $ProposedObject ` + -Property name)) + { + if(Compare-Object -ReferenceObject $ExistingObject ` + -DifferenceObject $ProposedObject ` + -Property type) + { + $ErrorMessage = $LocalizedData.ErrorWebsiteTestAutoStartProviderFailure + New-TerminatingError -ErrorId 'ErrorWebsiteTestAutoStartProviderFailure' ` + -ErrorMessage $ErrorMessage ` + -ErrorCategory 'InvalidResult'` + } + } + + return $true +} + +#endregion + +#region Bindings functions + +<# + .SYNOPSIS + Helper function used to validate that the website's binding information is unique to other + websites. Returns False if at least one of the bindings is already assigned to another + website. + + .PARAMETER Name + Specifies the name of the website. + + .PARAMETER ExcludeStopped + Omits stopped websites. + + .NOTES + This function tests standard ('http' and 'https') bindings only. + It is technically possible to assign identical non-standard bindings (such as 'net.tcp') + to different websites. +#> +function Confirm-UniqueBinding +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Name, + + [Parameter()] + [Switch] + $ExcludeStopped + ) + + $Website = Get-Website | Where-Object -FilterScript {$_.Name -eq $Name} + + if (-not $Website) + { + $ErrorMessage = $LocalizedData.ErrorWebsiteNotFound ` + -f $Name + New-TerminatingError -ErrorId 'WebsiteNotFound' ` + -ErrorMessage $ErrorMessage ` + -ErrorCategory 'InvalidResult' + } + + $ReferenceObject = @( + $Website.bindings.Collection | + Where-Object -FilterScript {$_.protocol -in @('http', 'https', 'ftp')} | + ConvertTo-WebBinding -Verbose:$false + ) + + if ($ExcludeStopped) + { + $OtherWebsiteFilter = {$_.Name -ne $Website.Name -and $_.State -ne 'Stopped'} + } + else + { + $OtherWebsiteFilter = {$_.Name -ne $Website.Name} + } + + $DifferenceObject = @( + Get-Website | + Where-Object -FilterScript $OtherWebsiteFilter | + ForEach-Object -Process {$_.bindings.Collection} | + Where-Object -FilterScript {$_.protocol -in @('http', 'https', 'ftp')} | + ConvertTo-WebBinding -Verbose:$false + ) + + # Assume that bindings are unique + $Result = $true + + $CompareSplat = @{ + ReferenceObject = $ReferenceObject + DifferenceObject = $DifferenceObject + Property = @('protocol', 'bindingInformation') + ExcludeDifferent = $true + IncludeEqual = $true + } + + if (Compare-Object @CompareSplat) + { + $Result = $false + } + + return $Result +} + +<# + .SYNOPSIS + Converts IIS elements to instances of the MSFT_xWebBindingInformation CIM class. +#> +function ConvertTo-CimBinding +{ + [CmdletBinding()] + [OutputType([Microsoft.Management.Infrastructure.CimInstance])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [AllowEmptyCollection()] + [AllowNull()] + [Object[]] + $InputObject + ) + begin + { + $cimNamespace = 'root/microsoft/Windows/DesiredStateConfiguration' + } + process + { + foreach ($binding in $InputObject) + { + [Hashtable]$CimProperties = @{ + Protocol = [String]$binding.protocol + BindingInformation = [String]$binding.bindingInformation + } + + $cimClassName = switch($CimProperties.Protocol) + { + 'ftp' { 'MSFT_FTPBindingInformation' } + default { 'MSFT_xWebBindingInformation' } + } + + if ($Binding.Protocol -in @('http', 'https', 'ftp')) + { + # Extract IPv6 address + if ($binding.bindingInformation -match '^\[(.*?)\]\:(.*?)\:(.*?)$') + { + $IPAddress = $Matches[1] + $Port = $Matches[2] + $HostName = $Matches[3] + } + else + { + $IPAddress, $Port, $HostName = $binding.bindingInformation -split '\:' + } + + if ([String]::IsNullOrEmpty($IPAddress)) + { + $IPAddress = '*' + } + + $cimProperties.Add('IPAddress', [String]$IPAddress) + $cimProperties.Add('Port', [UInt16]$Port) + $cimProperties.Add('HostName', [String]$HostName) + } + else + { + $cimProperties.Add('IPAddress', [String]::Empty) + $cimProperties.Add('Port', [UInt16]::MinValue) + $cimProperties.Add('HostName', [String]::Empty) + } + + if ($Binding.Protocol -ne 'ftp') + { + if ([Environment]::OSVersion.Version -ge '6.2') + { + $cimProperties.Add('SslFlags', [String]$binding.sslFlags) + } + + $cimProperties.Add('CertificateThumbprint', [String]$binding.certificateHash) + $cimProperties.Add('CertificateStoreName', [String]$binding.certificateStoreName) + } + + New-CimInstance -ClassName $cimClassName ` + -Namespace $cimNamespace ` + -Property $CimProperties ` + -ClientOnly + } + } +} + +<# + .SYNOPSIS + Converts instances of the MSFT_xWebBindingInformation CIM class to the IIS + element representation. + + .LINK + https://www.iis.net/configreference/system.applicationhost/sites/site/bindings/binding +#> +function ConvertTo-WebBinding +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [AllowEmptyCollection()] + [AllowNull()] + [Object[]] + $InputObject + ) + process + { + foreach ($binding in $InputObject) + { + $outputObject = @{ + protocol = $binding.Protocol + } + + if ($binding -is [Microsoft.Management.Infrastructure.CimInstance]) + { + if ($binding.Protocol -in @('http', 'https', 'ftp')) + { + if (-not [String]::IsNullOrEmpty($binding.BindingInformation)) + { + if (-not [String]::IsNullOrEmpty($binding.IPAddress) -or + -not [String]::IsNullOrEmpty($binding.Port) -or + -not [String]::IsNullOrEmpty($binding.HostName) + ) + { + $isJoinRequired = $true + Write-Verbose -Message ` + ($LocalizedData.VerboseConvertToWebBindingIgnoreBindingInformation ` + -f $binding.Protocol) + } + else + { + $isJoinRequired = $false + } + } + else + { + $isJoinRequired = $true + } + + # Construct the bindingInformation attribute + if ($isJoinRequired -eq $true) + { + $IPAddressString = Format-IPAddressString -InputString $binding.IPAddress ` + -ErrorAction Stop + + if ([String]::IsNullOrEmpty($binding.Port)) + { + switch ($binding.Protocol) + { + 'http' {$portNumberString = '80'} + 'https' {$portNumberString = '443'} + 'ftp' {$portNumberString = '21'} + } + + Write-Verbose -Message ` + ($LocalizedData.VerboseConvertToWebBindingDefaultPort ` + -f $binding.Protocol, $portNumberString) + } + else + { + if (Test-PortNumber -InputString $binding.Port) + { + $portNumberString = $binding.Port + } + else + { + $errorMessage = $LocalizedData.ErrorWebBindingInvalidPort ` + -f $binding.Port + New-TerminatingError -ErrorId 'WebBindingInvalidPort' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidArgument' + } + } + + $bindingInformation = $IPAddressString, ` + $portNumberString, ` + $binding.HostName -join ':' + $outputObject.Add('bindingInformation', [String]$bindingInformation) + } + else + { + $outputObject.Add('bindingInformation', [String]$binding.BindingInformation) + } + } + else + { + if ([String]::IsNullOrEmpty($binding.BindingInformation)) + { + $errorMessage = $LocalizedData.ErrorWebBindingMissingBindingInformation ` + -f $binding.Protocol + New-TerminatingError -ErrorId 'WebBindingMissingBindingInformation' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidArgument' + } + else + { + $outputObject.Add('bindingInformation', [String]$binding.BindingInformation) + } + } + + # SSL-related properties + if ($binding.Protocol -eq 'https') + { + if ([String]::IsNullOrEmpty($binding.CertificateThumbprint)) + { + If ($Binding.CertificateSubject) + { + if ($binding.CertificateSubject.substring(0,3) -ne 'CN=') + { + $binding.CertificateSubject = "CN=$($Binding.CertificateSubject)" + } + $FindCertificateSplat = @{ + Subject = $Binding.CertificateSubject + } + } + else + { + $errorMessage = $LocalizedData.ErrorWebBindingMissingCertificateThumbprint ` + -f $binding.Protocol + New-TerminatingError -ErrorId 'WebBindingMissingCertificateThumbprint' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidArgument' + } + } + + if ([String]::IsNullOrEmpty($binding.CertificateStoreName)) + { + $certificateStoreName = 'MY' + Write-Verbose -Message ` + ($LocalizedData.VerboseConvertToWebBindingDefaultCertificateStoreName ` + -f $certificateStoreName) + } + else + { + $certificateStoreName = $binding.CertificateStoreName + } + + if ($FindCertificateSplat) + { + $FindCertificateSplat.Add('Store',$CertificateStoreName) + $Certificate = Find-Certificate @FindCertificateSplat + if ($Certificate) + { + $certificateHash = $Certificate.Thumbprint + } + else + { + $errorMessage = $LocalizedData.ErrorWebBindingInvalidCertificateSubject ` + -f $binding.CertificateSubject, $binding.CertificateStoreName + New-TerminatingError -ErrorId 'WebBindingInvalidCertificateSubject' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidArgument' + } + } + + # Remove the Left-to-Right Mark character + if ($certificateHash) + { + $certificateHash = $certificateHash -replace '^\u200E' + } + else + { + $certificateHash = $binding.CertificateThumbprint -replace '^\u200E' + } + + $outputObject.Add('certificateHash', [String]$certificateHash) + $outputObject.Add('certificateStoreName', [String]$certificateStoreName) + + if ([Environment]::OSVersion.Version -ge '6.2') + { + $SslFlags = [Int64]$binding.SslFlags + + if ($SslFlags -in @(1, 3) -and [String]::IsNullOrEmpty($binding.HostName)) + { + $errorMessage = $LocalizedData.ErrorWebBindingMissingSniHostName + New-TerminatingError -ErrorId 'WebBindingMissingSniHostName' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidArgument' + } + + $outputObject.Add('sslFlags', $SslFlags) + } + } + elseif ($binding.Protocol -ne 'ftp') + { + # Ignore SSL-related properties for non-SSL bindings + $outputObject.Add('certificateHash', [String]::Empty) + $outputObject.Add('certificateStoreName', [String]::Empty) + + if ([Environment]::OSVersion.Version -ge '6.2') + { + $outputObject.Add('sslFlags', [Int64]0) + } + } + } + else + { + <# + WebAdministration can throw the following exception if there are non-standard + bindings (such as 'net.tcp'): 'The data is invalid. + (Exception from HRESULT: 0x8007000D)' + + Steps to reproduce: + 1) Add 'net.tcp' binding + 2) Execute {Get-Website | ` + ForEach-Object {$_.bindings.Collection} | ` + Select-Object *} + + Workaround is to create a new custom object and use dot notation to + access binding properties. + #> + + $outputObject.Add('bindingInformation', [String]$binding.bindingInformation) + + if ($binding.Protocol -ne 'ftp') + { + $outputObject.Add('certificateHash', [String]$binding.certificateHash) + $outputObject.Add('certificateStoreName', [String]$binding.certificateStoreName) + + if ([Environment]::OSVersion.Version -ge '6.2') + { + $outputObject.Add('sslFlags', [Int64]$binding.sslFlags) + } + } + } + + Write-Output -InputObject ([PSCustomObject]$outputObject) + } + } +} + +<# + .SYNOPSIS + Formats the input IP address string for use in the bindingInformation attribute. +#> +function Format-IPAddressString +{ + [CmdletBinding()] + [OutputType([String])] + param + ( + [Parameter(Mandatory = $true)] + [AllowEmptyString()] + [AllowNull()] + [String] + $InputString + ) + + if ([String]::IsNullOrEmpty($InputString) -or $InputString -eq '*') + { + $outputString = '*' + } + else + { + $ipAddress = Test-IPAddress $InputString + + switch ($ipAddress.AddressFamily) + { + 'InterNetwork' + { + $outputString = $ipAddress.IPAddressToString + } + 'InterNetworkV6' + { + $outputString = '[{0}]' -f $ipAddress.IPAddressToString + } + } + } + + return $outputString +} + +<# + .SYNOPSIS + Validates the desired binding information (i.e. no duplicate IP address, port, and + host name combinations). + + .PARAMETER BindingInfo + CIM Instances of the binding information. +#> +function Test-BindingInfo +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.Management.Infrastructure.CimInstance[]] + $BindingInfo + ) + + $isValid = $true + + try + { + # Normalize the input (helper functions will perform additional validations) + $bindings = @(ConvertTo-WebBinding -InputObject $bindingInfo | ConvertTo-CimBinding) + $standardBindings = @($bindings | ` + Where-Object -FilterScript {$_.Protocol -in @('http', 'https', 'ftp')}) + $nonStandardBindings = @($bindings | ` + Where-Object -FilterScript {$_.Protocol -notin @('http', 'https', 'ftp')}) + + if ($standardBindings.Count -ne 0) + { + # IP address, port, and host name combination must be unique + if (($standardBindings | Group-Object -Property IPAddress, Port, HostName) | ` + Where-Object -FilterScript {$_.Count -ne 1}) + { + $isValid = $false + Write-Verbose -Message ` + ($LocalizedData.VerboseTestBindingInfoSameIPAddressPortHostName) + } + + # A single port cannot be simultaneously specified for bindings with different protocols + foreach ($groupByPort in ($standardBindings | Group-Object -Property Port)) + { + if (($groupByPort.Group | Group-Object -Property Protocol).Length -ne 1) + { + $isValid = $false + Write-Verbose -Message ` + ($LocalizedData.VerboseTestBindingInfoSamePortDifferentProtocol) + break + } + } + } + + if ($nonStandardBindings.Count -ne 0) + { + if (($nonStandardBindings | ` + Group-Object -Property Protocol, BindingInformation) | ` + Where-Object -FilterScript {$_.Count -ne 1}) + { + $isValid = $false + Write-Verbose -Message ` + ($LocalizedData.VerboseTestBindingInfoSameProtocolBindingInformation) + } + } + } + catch + { + $isValid = $false + Write-Verbose -Message ($LocalizedData.VerboseTestBindingInfoInvalidCatch ` + -f $_.Exception.Message) + } + + return $isValid +} + +<# + .SYNOPSIS + Validates that an input string represents a valid port number. + The port number must be a positive integer between 1 and 65535. +#> +function Test-PortNumber +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [AllowEmptyString()] + [AllowNull()] + [String] + $InputString + ) + + try + { + $IsValid = [UInt16]$InputString -ne 0 + } + catch + { + $IsValid = $false + } + + return $IsValid +} + +<# + .SYNOPSIS + Validates that an input string represents a valid IP address. +#> +function Test-IPAddress +{ + [CmdletBinding()] + [OutputType([System.Net.IPAddress])] + param ( + [Parameter(Mandatory = $true)] + [System.String] + $InputString + ) + + try + { + $ipAddress = [IPAddress]::Parse($InputString) + } + catch + { + $errorMessage = $LocalizedData.ErrorWebBindingInvalidIPAddress ` + -f $InputString, $_.Exception.Message + New-TerminatingError -ErrorId 'WebBindingInvalidIPAddress' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidArgument' + } + + return $ipAddress +} + +<# + .SYNOPSIS + Helper function used to validate and compare website bindings of current to desired. + Returns True if bindings do not need to be updated. + + .PARAMETER Name + Specifies name of the website. + + .PARAMETER BindingInfo + CIM Instances of the binding information. +#> +function Test-WebsiteBinding +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Name, + + [Parameter(Mandatory = $true)] + [Microsoft.Management.Infrastructure.CimInstance[]] + $BindingInfo + ) + + $inDesiredState = $true + + <# + Ensure that desired binding information is valid (i.e. no duplicate IP address, port, and + host name combinations). + #> + if (-not (Test-BindingInfo -BindingInfo $BindingInfo)) + { + $errorMessage = $LocalizedData.ErrorWebsiteBindingInputInvalidation ` + -f $Name + New-TerminatingError -ErrorId 'WebsiteBindingInputInvalidation' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidResult' + } + + try + { + $website = Get-Website | Where-Object -FilterScript {$_.Name -eq $Name} + + # Normalize binding objects to ensure they have the same representation + $currentBindings = @(ConvertTo-WebBinding -InputObject $website.bindings.Collection ` + -Verbose:$false) + $desiredBindings = @(ConvertTo-WebBinding -InputObject $BindingInfo ` + -Verbose:$false) + + $propertiesToCompare = 'protocol', ` + 'bindingInformation', ` + 'certificateHash', ` + 'certificateStoreName' + + <# + The sslFlags attribute was added in IIS 8.0. + This check is needed for backwards compatibility with Windows Server 2008 R2. + #> + if ([Environment]::OSVersion.Version -ge '6.2') + { + $propertiesToCompare += 'sslFlags' + } + + if (Compare-Object -ReferenceObject $currentBindings ` + -DifferenceObject $desiredBindings ` + -Property $propertiesToCompare) + { + $inDesiredState = $false + } + } + catch + { + $errorMessage = $LocalizedData.ErrorWebsiteCompareFailure ` + -f $Name, $_.Exception.Message + New-TerminatingError -ErrorId 'WebsiteCompareFailure' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidResult' + } + + return $inDesiredState +} + +<# + .SYNOPSIS + Updates website bindings. + + .PARAMETER Name + Specifies name of the website. + + .PARAMETER BindingInfo + CIM Instances of the binding information. +#> +function Update-WebsiteBinding +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Name, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $BindingInfo + ) + + <# + Use Get-WebConfiguration instead of Get-Website to retrieve XPath of the target website. + XPath -Filter is case-sensitive. Use Where-Object to get the target website by name. + #> + $website = Get-WebConfiguration -Filter '/system.applicationHost/sites/site' | + Where-Object -FilterScript {$_.Name -eq $Name} + + if (-not $website) + { + $errorMessage = $LocalizedData.ErrorWebsiteNotFound ` + -f $Name + New-TerminatingError -ErrorId 'WebsiteNotFound' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidResult' + } + + ConvertTo-WebBinding -InputObject $BindingInfo -ErrorAction Stop | + ForEach-Object -Begin { + Clear-WebConfiguration -Filter "$($website.ItemXPath)/bindings" -Force -ErrorAction Stop + } -Process { + + $properties = $_ + + try + { + Add-WebConfiguration -Filter "$($website.ItemXPath)/bindings" -Value @{ + protocol = $properties.protocol + bindingInformation = $properties.bindingInformation + } -Force -ErrorAction Stop + } + catch + { + $errorMessage = $LocalizedData.ErrorWebsiteBindingUpdateFailure ` + -f $Name, $_.Exception.Message + New-TerminatingError -ErrorId 'WebsiteBindingUpdateFailure' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidResult' + } + + if ($properties.protocol -eq 'https') + { + if ([Environment]::OSVersion.Version -ge '6.2') + { + try + { + Set-WebConfigurationProperty ` + -Filter "$($website.ItemXPath)/bindings/binding[last()]" ` + -Name sslFlags ` + -Value $properties.sslFlags ` + -Force ` + -ErrorAction Stop + } + catch + { + $errorMessage = $LocalizedData.ErrorWebsiteBindingUpdateFailure ` + -f $Name, $_.Exception.Message + New-TerminatingError ` + -ErrorId 'WebsiteBindingUpdateFailure' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidResult' + } + } + + try + { + $binding = Get-WebConfiguration ` + -Filter "$($website.ItemXPath)/bindings/binding[last()]" ` + -ErrorAction Stop + $binding.AddSslCertificate($properties.certificateHash, ` + $properties.certificateStoreName) + } + catch + { + $errorMessage = $LocalizedData.ErrorWebBindingCertificate ` + -f $properties.certificateHash, $_.Exception.Message + New-TerminatingError ` + -ErrorId 'WebBindingCertificate' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidOperation' + } + } + } +} + +#endregion diff --git a/DSCResources/MSFT_FTP/MSFT_FTP.psm1 b/DSCResources/MSFT_FTP/MSFT_FTP.psm1 new file mode 100644 index 000000000..379eb70e2 --- /dev/null +++ b/DSCResources/MSFT_FTP/MSFT_FTP.psm1 @@ -0,0 +1,1700 @@ +# Load the Helper Module +Import-Module -Name "$PSScriptRoot\..\Helper.psm1" + +# Import Localization Strings +$localizedData = Get-LocalizedData ` + -ResourceName 'MSFT_FTP' ` + -ResourcePath (Split-Path -Parent $Script:MyInvocation.MyCommand.Path) + +<# + .SYNOPSIS + The Get-TargetResource cmdlet is used to fetch the status of the FTP Site on the + target machine. It gives the ftpSite info of the requested role/feature on the + target machine. + + .PARAMETER Name + Specifies the name of the FTP Site. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Name + ) + + Assert-Module + + $ftpSite = Get-Website | Where-Object -FilterScript {$_.Name -eq $Name} + $defaultFirewallSupport = Get-WebConfiguration -Filter '/system.ftpServer/firewallSupport' + $physicalPathCredential = [System.Management.Automation.PSCredential]::Empty + + if ($ftpSite.Count -eq 0) + { + Write-Verbose -Message ($LocalizedData.VerboseGetTargetAbsent) + $ensureResult = 'Absent' + } + elseif ($ftpSite.Count -eq 1) + { + $authenticationInfo = Get-AuthenticationInfo -Site $Name -IisType 'Ftp' + $authorizationInfo = Get-AuthorizationInfo -Site $Name + $sslInfo = Get-SslInfo -Site $Name + $bindings = @(ConvertTo-CimBinding -InputObject $ftpSite.bindings.Collection) + $logFlags = [array]$ftpSite.ftpServer.logFile.LogExtFileFlags.Split(',') + $showFlags = [array]$ftpSite.ftpServer.directoryBrowse.showFlags.Split(',') + + Write-Verbose -Message ($LocalizedData.VerboseGetTargetPresent) + $ensureResult = 'Present' + } + else # Multiple ftpSites with the same name exist. This is not supported and is an error + { + $errorMessage = $LocalizedData.ErrorftpSiteDiscoveryFailure -f $Name + New-TerminatingError -ErrorId 'ftpSiteDiscoveryFailure' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidResult' + } + + # Add all ftpSite properties to the hash table + return @{ + Ensure = $ensureResult + Name = $Name + PhysicalPath = $ftpSite.PhysicalPath + PhysicalPathAccessAccount = $ftpSite.userName + PhysicalPathAccessPass = $ftpSite.password + State = $ftpSite.State + ApplicationPool = $ftpSite.ApplicationPool + AuthenticationInfo = $authenticationInfo + AuthorizationInfo = $authorizationInfo + SslInfo = $sslInfo + BindingInfo = $bindings + FirewallIPAddress = $ftpServer.firewallSupport.externalIp4Address + StartingDataChannelPort = $defaultFirewallSupport.lowDataChannelPort + EndingDataChannelPort = $defaultFirewallSupport.highDataChannelPort + GreetingMessage = $ftpSite.ftpServer.messages.greetingMessage + ExitMessage = $ftpSite.ftpServer.messages.exitMessage + BannerMessage = $ftpSite.ftpServer.messages.bannerMessage + MaxClientsMessage = $ftpSite.ftpServer.messages.maxClientsMessage + SuppressDefaultBanner = $ftpSite.ftpServer.messages.suppressDefaultBanner + AllowLocalDetailedErrors = $ftpSite.ftpServer.messages.allowLocalDetailedErrors + ExpandVariablesInMessages = $ftpSite.ftpServer.messages.expandVariables + LogPath = $ftpSite.ftpServer.logFile.directory + LogFlags = $logFlags + LogPeriod = $ftpSite.ftpServer.logFile.period + LogtruncateSize = $ftpSite.ftpServer.logFile.truncateSize + LoglocalTimeRollover = $ftpSite.ftpServer.logFile.localTimeRollover + DirectoryBrowseFlags = $showFlags + UserIsolation = $ftpSite.ftpServer.userIsolation.mode + } +} + +<# + .SYNOPSIS + The Set-TargetResource cmdlet is used to create, delete or configure a ftpSite on the + target machine. + + .PARAMETER Name + Specifies the name of the FTP Site. + + .PARAMETER Ensure + Specifies whether the FTP site should be present. + + .PARAMETER PhysicalPath + Specifies physical folder location for FTP site. + + .PARAMETER PhysicalPathAccessAccount + Specifies username for access to physical path if required. + + .PARAMETER PhysicalPathAccessPass + Specifies password for access to physical path if required. + + .PARAMETER State + Specifies state of the FTP site whether it should be Started or Stopped. + + .PARAMETER ApplicationPool + Specifies name of the application pool to use. + + .PARAMETER AuthenticationInfo + Specifies the authentication settings for FTP site in the form of embedded instance of + the MSFT_FTPAuthenticationInformation CIM class. Possible properties are: Anonymous, Basic. + + .PARAMETER AuthorizationInfo + Specifies the authorization settings for FTP site in the form of array of embedded instances of + the MSFT_FTPAuthorizationInformation CIM class. Possible properties are: AccessType, Roles, + Permissions, Users. + + .PARAMETER SslInfo + Specifies the FTP over Secure Sockets Layer (SSL) settings for the FTP service in the + form of embedded instance of the MSFT_FTPSslInformation CIM class. Possible properties + are: ControlChannelPolicy, DataChannelPolicy, RequireSsl128, CertificateThumbprint, + CertificateStoreName. + + .PARAMETER BindingInfo + Specifies binding information for the FTP site in the form of embedded instance of the + MSFT_FTPBindingInformation CIM class. Possible properties are: Protocol, BindingInformation, + IPAddress, Port, HostName. + + .PARAMETER FirewallIPAddress + Specifies the external firewall IP address used for passive connections. + + .PARAMETER StartingDataChannelPort + Specifies starting port number in port range used for data connections in passive mode. + + .PARAMETER EndingDataChannelPort + Specifies ending port number in port range used for data connections in passive mode. + + .PARAMETER GreetingMessage + Specifies message the FTP server displays when FTP clients have logged in to the FTP server. + + .PARAMETER ExitMessage + Specifies message the FTP server displays when FTP clients log off the FTP server. + + .PARAMETER BannerMessage + Specifies message the FTP server displays when FTP clients first connect to the FTP server. + + .PARAMETER MaxClientsMessage + Specifies message when clients cannot connect because the FTP service has reached the maximum number of client connections allowed. + + .PARAMETER SuppressDefaultBanner + Specifies whether to display the default identification banner for the FTP server or not. + + .PARAMETER AllowLocalDetailedErrors + Specifies whether to display detailed error messages on the local host. + + .PARAMETER ExpandVariablesInMessages + Specifies whether to display a specific set of user variables in FTP messages. + + .PARAMETER LogFlags + Specifies the categories of information that are written to the log file. + + .PARAMETER LogPath + Specifies the directory to be used for storing logfiles. + + .PARAMETER LogPeriod + Specifies how often the FTP service creates a new log file. + + .PARAMETER LogTruncateSize + Specifies the maximum size of the log file (in bytes) after which to create a new log file. + This value is only applicable when MaxSize is chosen for the LogPeriod attribute. + + .PARAMETER LoglocalTimeRollover + Specifies whether new log file is created based on local time or UTC. + + .PARAMETER DirectoryBrowseFlags + Specifies content settings for directory browsing on FTP site. + + .PARAMETER UserIsolation + Specifies to which folder users access should be restricted on a single FTP server. +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Name, + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [String] + $Ensure = 'Present', + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String] + $PhysicalPath, + + [Parameter()] + [String] + $PhysicalPathAccessAccount, + + [Parameter()] + [String] + $PhysicalPathAccessPass, + + [Parameter()] + [ValidateSet('Started', 'Stopped')] + [String] + $State = 'Started', + + # The application pool name must contain between 1 and 64 characters + [Parameter()] + [ValidateLength(1, 64)] + [String] + $ApplicationPool, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $AuthenticationInfo, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $AuthorizationInfo, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $SslInfo, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $BindingInfo, + + [Parameter()] + [String] + $FirewallIPAddress, + + [Parameter()] + [ValidateScript({$_ -eq 0 -or $_ -in 1025 .. 65535})] + [uint16] + $StartingDataChannelPort, + + [Parameter()] + [ValidateScript({$_ -eq 0 -or $_ -in 1025 .. 65535})] + [uint16] + $EndingDataChannelPort, + + [Parameter()] + [String] + $GreetingMessage, + + [Parameter()] + [String] + $ExitMessage, + + [Parameter()] + [String] + $BannerMessage, + + [Parameter()] + [String] + $MaxClientsMessage, + + [Parameter()] + [Boolean] + $SuppressDefaultBanner, + + [Parameter()] + [Boolean] + $AllowLocalDetailedErrors, + + [Parameter()] + [Boolean] + $ExpandVariablesInMessages, + + [Parameter()] + [ValidateSet('Date','Time','ClientIP','UserName','ServerIP','Method','UriStem','UriQuery','HttpStatus','Win32Status','TimeTaken','ServerPort','UserAgent','Referer','HttpSubStatus')] + [String[]] + $LogFlags, + + [Parameter()] + [String] + $LogPath, + + [Parameter()] + [ValidateSet('Hourly','Daily','Weekly','Monthly','MaxSize')] + [String] + $LogPeriod, + + [Parameter()] + [ValidateRange('1048576','4294967295')] + [String] + $LogTruncateSize, + + [Parameter()] + [Boolean] + $LoglocalTimeRollover, + + [Parameter()] + [ValidateSet('StyleUnix','LongDate','DisplayAvailableBytes','DisplayVirtualDirectories')] + [String[]] + $DirectoryBrowseFlags, + + [Parameter()] + [ValidateSet('None','StartInUsersDirectory','IsolateAllDirectories','IsolateRootDirectoryOnly')] + [String] + $UserIsolation + ) + + Assert-Module + + $ftpSite = Get-Website | Where-Object -FilterScript {$_.Name -eq $Name} + + if ($Ensure -eq 'Present') + { + $iisType = 'Ftp' + $defaultFirewallSupport = Get-WebConfiguration -Filter '/system.ftpServer/firewallSupport' + + if ($null -eq $AuthenticationInfo) + { + $AuthenticationInfo = Get-DefaultAuthenticationInfo -IisType $IisType + } + + # Create ftpSite if it does not exist + if ($null -eq $ftpSite) + { + if ([string]::IsNullOrEmpty($PhysicalPath)) + { + throw 'The PhysicalPath Parameter must be provided for a ftpSite to be created!' + } + + try + { + $PSBoundParameters.GetEnumerator() | + Where-Object -FilterScript { + $_.Key -in (Get-Command -Name New-WebftpSite ` + -Module WebAdministration).Parameters.Keys + } | + ForEach-Object -Begin { + $NewftpSiteSplat = @{} + } -Process { + $NewftpSiteSplat.Add($_.Key, $_.Value) + } + + <# + If there are no other ftpSites, specify the Id Parameter for the new + ftpSite. Otherwise an error can occur on systems running + Windows Server 2008 R2. + #> + if (-not (Get-Website)) + { + $NewftpSiteSplat.Add('Id', 1) + } + + # Set default port to FTP:21 else it will be HTTP:80 + if(-not(($PSBoundParameters.ContainsKey('BindingIfo')))) + { + $NewftpSiteSplat.Add('Port', 21) + } + + if ([bool]([System.Uri]$PhysicalPath).IsUnc) + { + # If physical path is provided using Unc syntax run New-WebftpSite with -Force flag + $ftpSite = New-WebftpSite @NewftpSiteSplat -ErrorAction Stop -Force + } + else + { + # If physical path is provided don't run New-WebftpSite with -Force flag to verify that the path exists + $ftpSite = New-WebftpSite @NewftpSiteSplat -ErrorAction Stop + } + + Write-Verbose -Message ($LocalizedData.VerboseSetTargetftpSiteCreated ` + -f $Name) + } + catch + { + $errorMessage = $LocalizedData.ErrorftpSiteCreationFailure ` + -f $Name, $_.Exception.Message + New-TerminatingError -ErrorId 'ftpSiteCreationFailure' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidOperation' + } + } + + # Update Physical Path if required + if ([string]::IsNullOrEmpty($PhysicalPath) -eq $false -and ` + $ftpSite.PhysicalPath -ne $PhysicalPath) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name physicalPath ` + -Value $PhysicalPath ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdatedPhysicalPath ` + -f $Name) + } + + # Update physical path access username if required + if ($PSBoundParameters.ContainsKey('PhysicalPathAccessAccount') -and ` + $ftpSite.userName -ne $PhysicalPathAccessAccount) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name userName ` + -Value $PhysicalPathAccessAccount ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdatePhysicalPathAccessAccount ` + -f $Name) + } + + # Update physical path access password if required + if ($PSBoundParameters.ContainsKey('PhysicalPathAccessPass') -and ` + $ftpSite.password -ne $PhysicalPathAccessPass) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name password ` + -Value $PhysicalPathAccessPass ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdatePhysicalPathAccessPass ` + -f $Name) + } + + # Update Application Pool if required + if ($PSBoundParameters.ContainsKey('ApplicationPool') -and ` + $ftpSite.ApplicationPool -ne $ApplicationPool) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name applicationPool ` + -Value $ApplicationPool ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdatedApplicationPool ` + -f $Name, $ApplicationPool) + } + + <# + Update Authentication if required; + if not defined then pass in DefaultAuthenticationInfo + #> + if (-not (Test-AuthenticationInfo -Site $Name ` + -IisType $IisType ` + -AuthenticationInfo $AuthenticationInfo)) + { + Set-AuthenticationInfo -Site $Name ` + -IisType $IisType ` + -AuthenticationInfo $AuthenticationInfo ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetAuthenticationInfoUpdated ` + -f $Name) + } + + # Update AuthorizationInfo if required + if ($PSBoundParameters.ContainsKey('AuthorizationInfo') -and ` + (-not (Test-AuthorizationInfo -Site $Name ` + -AuthorizationInfo $AuthorizationInfo))) + { + Set-FTPAuthorization -AuthorizationInfo $AuthorizationInfo ` + -Site $Name ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetAuthorizationInfoUpdated ` + -f $Name) + } + + # Update Bindings if required + if ($PSBoundParameters.ContainsKey('BindingInfo') -and ` + $null -ne $BindingInfo) + { + if (-not (Test-WebsiteBinding -Name $Name ` + -BindingInfo $BindingInfo)) + { + Update-WebsiteBinding -Name $Name ` + -BindingInfo $BindingInfo ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdatedBindingInfo ` + -f $Name) + } + } + + # Update SslInfo if required + if ($PSBoundParameters.ContainsKey('SslInfo') -and ` + (-not (Confirm-UniqueSslInfo -Site $Name -SslInfo $SslInfo))) + { + Set-SslInfo -Site $Name -SslInfo $SslInfo -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateSslInfo ` + -f $name) + } + + # Update external firewall IP address if required + if ($PSBoundParameters.ContainsKey('FirewallIPAddress') -and ` + $FirewallIPAddress -ne $ftpSite.ftpServer.firewallSupport.externalIp4Address) + { + if ($FirewallIPAddress) + { + Test-IPAddress $FirewallIPAddress | Out-Null + } + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpServer.firewallSupport.externalIp4Address ` + -Value $FirewallIPAddress ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateExternalIPaddress -f $Name) + } + + # Update starting data channel port number + if ($PSBoundParameters.ContainsKey('StartingDataChannelPort') -and ` + $StartingDataChannelPort -ne $defaultFirewallSupport.lowDataChannelPort) + { + Set-WebConfigurationProperty ` + -Filter '/system.ftpServer/firewallSupport' ` + -Name lowDataChannelPort ` + -Value $StartingDataChannelPort ` + -Force ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateStartingDataChannelPort -f $Name) + } + + # Update ending data channel port number + if ($PSBoundParameters.ContainsKey('EndingDataChannelPort') -and ` + $EndingDataChannelPort -ne $defaultFirewallSupport.highDataChannelPort) + { + Set-WebConfigurationProperty ` + -Filter '/system.ftpServer/firewallSupport' ` + -Name highDataChannelPort ` + -Value $EndingDataChannelPort ` + -Force ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateEndingDataChannelPort -f $Name) + } + + # Update greeting message + if ($PSBoundParameters.ContainsKey('GreetingMessage') -and ` + $GreetingMessage -ne $ftpSite.ftpServer.messages.greetingMessage) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpServer.messages.greetingMessage ` + -Value $GreetingMessage ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateGreetingMessage -f $Name) + } + + # Update exit message + if ($PSBoundParameters.ContainsKey('ExitMessage') -and ` + $ExitMessage -ne $ftpSite.ftpServer.messages.exitMessage) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpServer.messages.exitMessage ` + -Value $ExitMessage ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateExitMessage -f $Name) + } + + # Update banner message + if ($PSBoundParameters.ContainsKey('BannerMessage') -and ` + $BannerMessage -ne $ftpSite.ftpServer.messages.bannerMessage) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpServer.messages.bannerMessage ` + -Value $BannerMessage ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateBannerMessage -f $Name) + } + + # Update maximum client connections reached message + if ($PSBoundParameters.ContainsKey('MaxClientsMessage') -and ` + $MaxClientsMessage -ne $ftpSite.ftpServer.messages.maxClientsMessage) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpServer.messages.maxClientsMessage ` + -Value $MaxClientsMessage ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateMaxClientsMessage -f $Name) + } + + # Update default banner suppression + if ($PSBoundParameters.ContainsKey('SuppressDefaultBanner') -and ` + $SuppressDefaultBanner -ne $ftpSite.ftpServer.messages.suppressDefaultBanner) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpServer.messages.suppressDefaultBanner ` + -Value $SuppressDefaultBanner ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateSuppressDefaultBanner -f $Name) + } + + # Update allowance of detailed errors locally + if ($PSBoundParameters.ContainsKey('AllowLocalDetailedErrors') -and ` + $AllowLocalDetailedErrors -ne $ftpSite.ftpServer.messages.allowLocalDetailedErrors) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpServer.messages.allowLocalDetailedErrors ` + -Value $AllowLocalDetailedErrors ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateAllowLocalDetailedErrors -f $Name) + } + + # Update expansion of user variables in messages + if ($PSBoundParameters.ContainsKey('ExpandVariablesInMessages') -and ` + $ExpandVariablesInMessages -ne $ftpSite.ftpServer.messages.expandVariables) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpServer.messages.expandVariables ` + -Value $ExpandVariablesInMessages ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateExpandVariablesInMessages -f $Name) + } + + # Update LogFlags if required + if ($PSBoundParameters.ContainsKey('LogFlags') -and ` + (-not (Compare-LogFlags -Name $Name ` + -LogFlags $LogFlags -FtpSite))) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpServer.logFile.logExtFileFlags ` + -Value ($LogFlags -join ',') ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateLogFlags ` + -f $Name) + } + + # Update LogPath if required + if ($PSBoundParameters.ContainsKey('LogPath') -and ` + ($LogPath -ne $ftpSite.ftpServer.logFile.directory)) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpServer.logFile.directory ` + -Value $LogPath ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateLogPath ` + -f $Name) + } + + # Update LogPeriod if needed + if ($PSBoundParameters.ContainsKey('LogPeriod') -and ` + ($LogPeriod -ne $ftpSite.ftpServer.logFile.period)) + { + if ($PSBoundParameters.ContainsKey('LogTruncateSize')) + { + Write-Verbose -Message ($LocalizedData.WarningLogPeriod -f $Name) + } + else + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpServer.logFile.period ` + -Value $LogPeriod ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateLogPeriod -f $name) + } + } + + # Update LogTruncateSize if needed + if ($PSBoundParameters.ContainsKey('LogTruncateSize') -and ` + ($LogTruncateSize -ne $ftpSite.ftpServer.logFile.truncateSize)) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpserver.logFile.truncateSize ` + -Value $LogTruncateSize ` + -ErrorAction Stop + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpserver.logFile.period ` + -Value 'MaxSize' ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateLogTruncateSize ` + -f $Name) + } + + # Update LoglocalTimeRollover if neeed + if ($PSBoundParameters.ContainsKey('LoglocalTimeRollover') -and ` + ($LoglocalTimeRollover -ne ` + ([System.Convert]::ToBoolean($ftpSite.ftpServer.logFile.localTimeRollover)))) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpserver.logFile.localTimeRollover ` + -Value $LoglocalTimeRollover + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateLoglocalTimeRollover ` + -f $Name) + } + + # Update DirectoryBrowse if required + if ($PSBoundParameters.ContainsKey('DirectoryBrowseFlags') -and ` + (-not (Compare-DirectoryBrowseFlags -Site $Name ` + -DirectoryBrowseFlags $DirectoryBrowseFlags))) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpserver.directoryBrowse.showFlags ` + -Value ($DirectoryBrowseFlags -join ',') ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateDirectoryBrowseFlags ` + -f $Name) + } + + # Update UserIsolation if required + if ($PSBoundParameters.ContainsKey('UserIsolation') -and ` + ($UserIsolation -ne $ftpSite.ftpServer.userIsolation.mode)) + { + Set-ItemProperty -Path "IIS:\Sites\$Name" ` + -Name ftpServer.userIsolation.mode ` + -Value $UserIsolation ` + -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdateUserIsolation ` + -f $Name) + } + + # Update State if required + if ($PSBoundParameters.ContainsKey('State') -and ` + $ftpSite.State -ne $State) + { + if ($State -eq 'Started') + { + try + { + Write-Verbose -Message ($LocalizedData.VerboseStartWebsite ` + -f $Name) + Start-Website -Name $Name -ErrorAction Stop + } + catch + { + $errorMessage = $LocalizedData.ErrorftpSiteStateFailure ` + -f $Name, $_.Exception.Message + New-TerminatingError -ErrorId 'WebsiteStateFailure' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidOperation' + } + } + else + { + try + { + Write-Verbose -Message ($LocalizedData.VerboseStopWebsite ` + -f $Name) + Stop-Website -Name $Name -ErrorAction Stop + } + catch + { + $errorMessage = $LocalizedData.ErrorftpSiteStateFailure ` + -f $Name, $_.Exception.Message + New-TerminatingError -ErrorId 'WebsiteStateFailure' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidOperation' + } + } + + Write-Verbose -Message ($LocalizedData.VerboseSetTargetUpdatedState ` + -f $Name) + } + } + else # Remove ftpSite + { + try + { + Remove-Website -Name $Name -ErrorAction Stop + Write-Verbose -Message ($LocalizedData.VerboseSetTargetftpSiteRemoved ` + -f $Name) + } + catch + { + $errorMessage = $LocalizedData.ErrorftpSiteRemovalFailure ` + -f $Name, $_.Exception.Message + New-TerminatingError -ErrorId 'ftpSiteRemovalFailure' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidOperation' + } + } +} + +<# + .SYNOPSIS + The Test-TargetResource cmdlet is used to validate if the role or feature is in a state as + expected in the instance document. + + .PARAMETER Name + Specifies the name of the FTP Site. + + .PARAMETER Ensure + Specifies whether the FTP site should be present. + + .PARAMETER PhysicalPath + Specifies physical folder location for FTP site. + + .PARAMETER PhysicalPathAccessAccount + Specifies username for access to physical path if required. + + .PARAMETER PhysicalPathAccessPass + Specifies password for access to physical path if required. + + .PARAMETER State + Specifies state of the FTP site whether it should be Started or Stopped. + + .PARAMETER ApplicationPool + Specifies name of the application pool to use. + + .PARAMETER AuthenticationInfo + Specifies the authentication settings for FTP site in the form of embedded instance of + the MSFT_FTPAuthenticationInformation CIM class. Possible properties are: Anonymous, Basic. + + .PARAMETER AuthorizationInfo + Specifies the authorization settings for FTP site in the form of array of embedded instances of + the MSFT_FTPAuthorizationInformation CIM class. Possible properties are: AccessType, Roles, + Permissions, Users. + + .PARAMETER SslInfo + Specifies the FTP over Secure Sockets Layer (SSL) settings for the FTP service in the + form of embedded instance of the MSFT_FTPSslInformation CIM class. Possible properties + are: ControlChannelPolicy, DataChannelPolicy, RequireSsl128, CertificateThumbprint, + CertificateStoreName. + + .PARAMETER BindingInfo + Specifies binding information for the FTP site in the form of embedded instance of the + MSFT_FTPBindingInformation CIM class. Possible properties are: Protocol, BindingInformation, + IPAddress, Port, HostName. + + .PARAMETER FirewallIPAddress + Specifies the external firewall IP address used for passive connections. + + .PARAMETER StartingDataChannelPort + Specifies starting port number in port range used for data connections in passive mode. + + .PARAMETER EndingDataChannelPort + Specifies ending port number in port range used for data connections in passive mode. + + .PARAMETER GreetingMessage + Specifies message the FTP server displays when FTP clients have logged in to the FTP server. + + .PARAMETER ExitMessage + Specifies message the FTP server displays when FTP clients log off the FTP server. + + .PARAMETER BannerMessage + Specifies message the FTP server displays when FTP clients first connect to the FTP server. + + .PARAMETER MaxClientsMessage + Specifies message when clients cannot connect because the FTP service has reached the maximum number of client connections allowed. + + .PARAMETER SuppressDefaultBanner + Specifies whether to display the default identification banner for the FTP server or not. + + .PARAMETER AllowLocalDetailedErrors + Specifies whether to display detailed error messages on the local host. + + .PARAMETER ExpandVariablesInMessages + Specifies whether to display a specific set of user variables in FTP messages. + + .PARAMETER LogFlags + Specifies the categories of information that are written to the log file. + + .PARAMETER LogPath + Specifies the directory to be used for storing logfiles. + + .PARAMETER LogPeriod + Specifies how often the FTP service creates a new log file. + + .PARAMETER LogTruncateSize + Specifies the maximum size of the log file (in bytes) after which to create a new log file. + This value is only applicable when MaxSize is chosen for the LogPeriod attribute. + + .PARAMETER LoglocalTimeRollover + Specifies whether new log file is created based on local time or UTC. + + .PARAMETER DirectoryBrowseFlags + Specifies content settings for directory browsing on FTP site. + + .PARAMETER UserIsolation + Specifies to which folder users access should be restricted on a single FTP server. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Name, + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [String] + $Ensure = 'Present', + + [Parameter()] + [ValidateNotNullOrEmpty()] + [String] + $PhysicalPath, + + [Parameter()] + [String] + $PhysicalPathAccessAccount, + + [Parameter()] + [String] + $PhysicalPathAccessPass, + + [Parameter()] + [ValidateSet('Started', 'Stopped')] + [String] + $State = 'Started', + + # The application pool name must contain between 1 and 64 characters + [Parameter()] + [ValidateLength(1, 64)] + [String] + $ApplicationPool, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $AuthenticationInfo, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $AuthorizationInfo, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $SslInfo, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $BindingInfo, + + [Parameter()] + [String] + $FirewallIPAddress, + + [Parameter()] + [ValidateScript({$_ -eq 0 -or $_ -in 1025 .. 65535})] + [uint16] + $StartingDataChannelPort, + + [Parameter()] + [ValidateScript({$_ -eq 0 -or $_ -in 1025 .. 65535})] + [uint16] + $EndingDataChannelPort, + + [Parameter()] + [String] + $GreetingMessage, + + [Parameter()] + [String] + $ExitMessage, + + [Parameter()] + [String] + $BannerMessage, + + [Parameter()] + [String] + $MaxClientsMessage, + + [Parameter()] + [Boolean] + $SuppressDefaultBanner, + + [Parameter()] + [Boolean] + $AllowLocalDetailedErrors, + + [Parameter()] + [Boolean] + $ExpandVariablesInMessages, + + [Parameter()] + [ValidateSet('Date','Time','ClientIP','UserName','ServerIP','Method','UriStem','UriQuery','HttpStatus','Win32Status','TimeTaken','ServerPort','UserAgent','Referer','HttpSubStatus')] + [String[]] + $LogFlags, + + [Parameter()] + [String] + $LogPath, + + [Parameter()] + [ValidateSet('Hourly','Daily','Weekly','Monthly','MaxSize')] + [String] + $LogPeriod, + + [Parameter()] + [ValidateRange('1048576','4294967295')] + [String] + $LogTruncateSize, + + [Parameter()] + [Boolean] + $LoglocalTimeRollover, + + [Parameter()] + [ValidateSet('StyleUnix','LongDate','DisplayAvailableBytes','DisplayVirtualDirectories')] + [String[]] + $DirectoryBrowseFlags, + + [Parameter()] + [ValidateSet('None','StartInUsersDirectory','IsolateAllDirectories','IsolateRootDirectoryOnly')] + [String] + $UserIsolation + ) + + Assert-Module + + $InDesiredState = $true + + $ftpSite = Get-Website | Where-Object -FilterScript {$_.Name -eq $Name} + + # Check Ensure + if (($Ensure -eq 'Present' -and $null -eq $ftpSite) -or ` + ($Ensure -eq 'Absent' -and $null -ne $ftpSite)) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseEnsure ` + -f $Name) + } + + # Only check properties if website exists + if ($Ensure -eq 'Present' -and ` + $null -ne $ftpSite) + { + $iisType = 'Ftp' + $defaultFirewallSupport = Get-WebConfiguration -Filter '/system.ftpServer/firewallSupport' + + if ($null -eq $AuthenticationInfo) + { + $AuthenticationInfo = Get-DefaultAuthenticationInfo -IisType $IisType + } + + # Check Physical Path property + if ([string]::IsNullOrEmpty($PhysicalPath) -eq $false -and ` + $ftpSite.PhysicalPath -ne $PhysicalPath) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalsePhysicalPath -f $Name) + } + + # Check physical path access username if required + if ($PSBoundParameters.ContainsKey('PhysicalPathAccessAccount') -and ` + $ftpSite.userName -ne $PhysicalPathAccessAccount) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalsePhysicalPathAccessAccount -f $Name) + } + + # Check physical path access password if required + if ($PSBoundParameters.ContainsKey('PhysicalPathAccessPass') -and ` + $ftpSite.password -ne $PhysicalPathAccessPass) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalsePhysicalPathAccessPass -f $Name) + } + + # Check State + if ($PSBoundParameters.ContainsKey('State') -and $ftpSite.State -ne $State) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseState -f $Name) + } + + # Check Application Pool property + if ($PSBoundParameters.ContainsKey('ApplicationPool') -and ` + $ftpSite.ApplicationPool -ne $ApplicationPool) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseApplicationPool -f $Name) + } + + # Check AuthenticationInfo + if (-not (Test-AuthenticationInfo -Site $Name ` + -IisType $IisType ` + -AuthenticationInfo $AuthenticationInfo)) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseAuthenticationInfo -f $Name) + } + + # Check Authorization + if ($PSBoundParameters.ContainsKey('AuthorizationInfo') -and ` + (-not (Test-AuthorizationInfo -Site $Name ` + -AuthorizationInfo $AuthorizationInfo))) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseAuthorizationInfo -f $Name) + } + + # Check Binding properties + if ($PSBoundParameters.ContainsKey('BindingInfo') -and ` + $null -ne $BindingInfo) + { + if (-not (Test-WebsiteBinding -Name $Name -BindingInfo $BindingInfo)) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseBindingInfo -f $Name) + } + } + + # Check SslInfo + if ($PSBoundParameters.ContainsKey('SslInfo') -and ` + (-not (Confirm-UniqueSslInfo -Site $Name -SslInfo $SslInfo))) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseSslInfo -f $Name) + } + + # Check external firewall IP address + if ($PSBoundParameters.ContainsKey('FirewallIPAddress') -and ` + $FirewallIPAddress -ne $ftpSite.ftpServer.firewallSupport.externalIp4Address) + { + if ($FirewallIPAddress) + { + Test-IPAddress $FirewallIPAddress | Out-Null + } + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseExternalIPaddress -f $Name) + } + + # Check starting data channel port number + if ($PSBoundParameters.ContainsKey('StartingDataChannelPort') -and ` + $StartingDataChannelPort -ne $defaultFirewallSupport.lowDataChannelPort) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseStartingDataChannelPort -f $Name) + } + + # Check ending data channel port number + if ($PSBoundParameters.ContainsKey('EndingDataChannelPort') -and ` + $EndingDataChannelPort -ne $defaultFirewallSupport.highDataChannelPort) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseEndingDataChannelPort -f $Name) + } + + # Check greeting message + if ($PSBoundParameters.ContainsKey('GreetingMessage') -and ` + $GreetingMessage -ne $ftpSite.ftpServer.messages.greetingMessage) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseGreetingMessage -f $Name) + } + + # Check exit message + if ($PSBoundParameters.ContainsKey('ExitMessage') -and ` + $ExitMessage -ne $ftpSite.ftpServer.messages.exitMessage) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseExitMessage -f $Name) + } + + # Check banner message + if ($PSBoundParameters.ContainsKey('BannerMessage') -and ` + $BannerMessage -ne $ftpSite.ftpServer.messages.bannerMessage) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseBannerMessage -f $Name) + } + + # Check maximum client connections reached message + if ($PSBoundParameters.ContainsKey('MaxClientsMessage') -and ` + $MaxClientsMessage -ne $ftpSite.ftpServer.messages.maxClientsMessage) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseMaxClientsMessage -f $Name) + } + + # Check default banner suppression + if ($PSBoundParameters.ContainsKey('SuppressDefaultBanner') -and ` + $SuppressDefaultBanner -ne $ftpSite.ftpServer.messages.suppressDefaultBanner) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseSuppressDefaultBanner -f $Name) + } + + # Check allowance of detailed errors locally + if ($PSBoundParameters.ContainsKey('AllowLocalDetailedErrors') -and ` + $AllowLocalDetailedErrors -ne $ftpSite.ftpServer.messages.allowLocalDetailedErrors) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseAllowLocalDetailedErrors -f $Name) + } + + # Check expansion of user variables in messages + if ($PSBoundParameters.ContainsKey('ExpandVariablesInMessages') -and ` + $ExpandVariablesInMessages -ne $ftpSite.ftpServer.messages.expandVariables) + { + $InDesiredState = $false + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseExpandVariablesInMessages -f $Name) + } + + # Check LogFlags + if ($PSBoundParameters.ContainsKey('LogFlags') -and ` + (-not (Compare-LogFlags -Name $Name -LogFlags $LogFlags -FtpSite))) + { + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseLogFlags -f $Name) + return $false + } + + # Check LogPath + if ($PSBoundParameters.ContainsKey('LogPath') -and ` + ($LogPath -ne $ftpSite.ftpServer.logFile.directory)) + { + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseLogPath -f $Name) + return $false + } + + # Check LogPeriod + if ($PSBoundParameters.ContainsKey('LogPeriod') -and ` + ($LogPeriod -ne $ftpSite.ftpServer.logFile.period)) + { + if ($PSBoundParameters.ContainsKey('LogTruncateSize')) + { + Write-Verbose -Message ($LocalizedData.WarningLogPeriod -f $Name) + } + else + { + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseLogPeriod -f $Name) + return $false + } + } + + # Check LogTruncateSize + if ($PSBoundParameters.ContainsKey('LogTruncateSize') -and ` + ($LogTruncateSize -ne $ftpSite.ftpServer.logFile.truncateSize)) + { + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseLogTruncateSize -f $Name) + return $false + } + + # Check LoglocalTimeRollover + if ($PSBoundParameters.ContainsKey('LoglocalTimeRollover') -and ` + ($LoglocalTimeRollover -ne ` + ([System.Convert]::ToBoolean($ftpSite.ftpServer.logFile.localTimeRollover)))) + { + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseLoglocalTimeRollover -f $Name) + return $false + } + + # Check DirectoryBrowseFlags + if ($PSBoundParameters.ContainsKey('DirectoryBrowseFlags') -and ` + (-not (Compare-DirectoryBrowseFlags -Site $Name ` + -DirectoryBrowseFlags $DirectoryBrowseFlags))) + { + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseDirectoryBrowseFlags -f $Name) + return $false + } + + # Check UserIsolation + if ($PSBoundParameters.ContainsKey('UserIsolation') -and ` + ($UserIsolation -ne $ftpSite.ftpServer.userIsolation.mode)) + { + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseUserIsolation -f $Name) + return $false + } + } + + if ($InDesiredState -eq $true) + { + Write-Verbose -Message ($LocalizedData.VerboseTestTargetTrueResult) + } + else + { + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseResult) + } + + return $InDesiredState +} + +# region Helper Functions + +<# + .SYNOPSIS + Helper function used to validate the DirectoryBrowse status. + Returns False if the DirectoryBrowseflags do not match and true if they do. + + .PARAMETER DirectoryBrowseflags + Specifies flags to check. + + .PARAMETER Site + Specifies the name of the FTP Site. +#> +function Compare-DirectoryBrowseFlags +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateSet('StyleUnix','LongDate','DisplayAvailableBytes','DisplayVirtualDirectories')] + [String[]] + $DirectoryBrowseflags, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $Site + + ) + + $CurrentDirectoryBrowseflags = (Get-Website -Name $Site).ftpServer.directoryBrowse.showFlags ` + -split ',' | Sort-Object + $ProposedDirectoryBrowseflags = $DirectoryBrowseflags ` + -split ',' | Sort-Object + + if (Compare-Object -ReferenceObject $CurrentDirectoryBrowseflags ` + -DifferenceObject $ProposedDirectoryBrowseflags) + { + return $false + } + + return $true +} + +<# + .SYNOPSIS + Helper function used to validate that the Authorization is unique to current + per CimInstance of MSFT_FTPAuthorizationInformation. + + .PARAMETER CurrentAuthorizationCollection + Specifies PSCustomObject of the current Authorization collection defined on the + ftpsite. + + .PARAMETER Authorization + Specifies the CIM of the single desired Authorization definition. + + .PARAMETER Property + Key property to check against. + + .NOTES + Compare-Object can be a bit weird so the approach to checking is done slightly + different in this function. +#> +function Confirm-UniqueFTPAuthorization +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [PSCustomObject[]] + $CurrentAuthorizationCollection, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimInstance] + $Authorization, + + [Parameter(Mandatory = $true)] + [ValidateSet('users','roles')] + [String] + $Property + ) + + $desiredObject = New-Object -TypeName PSObject -Property @{ + accessType = $Authorization.accessType + users = $Authorization.users + roles = $Authorization.roles + permissions = $Authorization.permissions + } + + $existingFtpAuthorizationInfo = $CurrentAuthorizationCollection | ` + Where-Object -Property $Property -eq -Value $Authorization.$Property | ` + Select-Object accessType,users,roles,permissions + + $existingObject=$() + $existingObject += foreach($existingAuthorization in $existingFtpAuthorizationInfo) + { + $currentObject = New-Object -TypeName PSObject ` + -Property @{ + accessType = $existingAuthorization.accessType + users = $existingAuthorization.users + roles = $existingAuthorization.roles + permissions = $existingAuthorization.permissions + } + + $compare = Compare-Object ` + -ReferenceObject $($existingAuthorization) ` + -DifferenceObject $($desiredObject) ` + -Property $Property,accessType,permissions + + $null -eq $compare + } + + if (-not $existingObject -or $true -notin $existingObject) + { + return $false + } + + return $true +} + +<# + .SYNOPSIS + Helper function used to validate that the SslInfo needs to be changed + + .PARAMETER Site + Specifies the name of the FTP Site. + + .PARAMETER SslInfo + Specifies the CIM of the SslInfo. +#> +function Confirm-UniqueSslInfo +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Site, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimInstance] + $SslInfo + ) + + $Store = $SslInfo.CertificateStoreName + $Hash = $SslInfo.CertificateThumbprint + + if($null -ne $Hash -and -not(Test-Path -Path Cert:\LocalMachine\${Store}\${Hash})) + { + $errorMessage = $LocalizedData.ErrorServerCertHashFailure ` + -f $SslInfo.CertificateThumbprint,$SslInfo.CertificateStoreName + New-TerminatingError -ErrorId 'ErrorServerCertHashFailure' ` + -ErrorMessage $errorMessage ` + -ErrorCategory 'InvalidResult' + } + + $Properties = @() + $proposedObject = New-Object -TypeName PSObject + foreach ($value in ($SslInfo.CimInstanceProperties | Where-Object {$null -ne $_.Value}).Name) + { + $correctValue = switch($value) + { + CertificateThumbprint { 'serverCertHash' } + CertificateStoreName { 'serverCertStoreName' } + RequireSsl128 { 'ssl128' } + ControlChannelPolicy { 'controlChannelPolicy' } + DataChannelPolicy { 'dataChannelPolicy' } + } + $proposedObject | Add-Member -Type NoteProperty -Name $correctValue -Value $SslInfo.$value + $Properties += $correctValue + } + + $currentSslInfo = ((Get-Website -Name $Site).ftpServer.security.ssl) + $existingObject = $currentSslInfo | Select-Object $Properties + + $compare = Compare-Object -ReferenceObject $existingObject ` + -DifferenceObject $proposedObject ` + -Property $Properties + + if($null -ne $compare) + { + return $false + } + + return $true +} + +<# + .SYNOPSIS + Helper function used to get the AuthorizationInfo for use in Get-TargetResource + + .PARAMETER Site + Specifies the name of the FTP Site. +#> +function Get-AuthorizationInfo +{ + [CmdletBinding()] + [OutputType([Microsoft.Management.Infrastructure.CimInstance])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Site + ) + + $authCollections = (Get-WebConfiguration ` + -Filter '/system.ftpServer/security/authorization' ` + -Location $Site).Collection + + $authorizationInfo = @() + foreach($authCollection in $authCollections) + { + $authorizationProperties = @{} + foreach ($type in @('accessType', 'users', 'roles', 'permissions')) + { + $authorizationProperties[$type] = [String]$authCollection.${type} + } + + $authorizationInfo += New-CimInstance ` + -ClassName MSFT_FTPAuthorizationInformation ` + -ClientOnly -Property $authorizationProperties ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + } + + return $authorizationInfo +} + +<# + .SYNOPSIS + Helper function used to get the SslInfo for use in Get-TargetResource. + + .PARAMETER Site + Specifies the name of the FTP Site. +#> +function Get-SslInfo +{ + [CmdletBinding()] + [OutputType([Microsoft.Management.Infrastructure.CimInstance])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Site + ) + + $sslProperties = @{} + foreach ($type in @('controlChannelPolicy', 'dataChannelPolicy', 'ssl128', 'serverCertHash', 'serverCertStoreName')) + { + $correctValue = switch($type) + { + serverCertHash { 'CertificateThumbprint' } + serverCertStoreName { 'CertificateStoreName' } + ssl128 { 'RequireSsl128' } + controlChannelPolicy { 'ControlChannelPolicy' } + dataChannelPolicy { 'DataChannelPolicy' } + } + + if ($type -eq 'ssl128') + { + $sslProperties[$correctValue] = [Boolean](Get-Item -Path IIS:\Sites\${Site}\).ftpServer.security.ssl.${type} + } + else + { + $sslProperties[$correctValue] = [String](Get-Item -Path IIS:\Sites\${Site}\).ftpServer.security.ssl.${type} + } + } + + return New-CimInstance -ClassName MSFT_FTPSslInformation ` + -ClientOnly -Property $sslProperties ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' +} + +<# + .SYNOPSIS + Helper function used to set the AuthorizationInfo. + + .PARAMETER Site + Specifies the name of the FTP Site. + + .PARAMETER AuthorizationInfo + Specifies the CIM of the AuthorizationInfo. +#> +function Set-FTPAuthorization +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Site, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $AuthorizationInfo + ) + + Clear-WebConfiguration ` + -Filter '/system.ftpServer/security/authorization' ` + -Location $Site ` + -PSPath 'IIS:\' ` + -Force ` + -ErrorAction Stop + + foreach ($authInfo in $AuthorizationInfo) + { + Add-WebConfiguration ` + -Filter '/system.ftpServer/security/authorization' ` + -Value @{ + accessType = $authInfo.accessType; + roles = $authInfo.roles; + permissions = $authInfo.permissions; + users = $authInfo.users + } ` + -PSPath IIS:\ ` + -Location $Site + } +} + +<# + .SYNOPSIS + Helper function used to set the SslInfo. + + .PARAMETER Site + Specifies the name of the FTP Site. + + .PARAMETER AuthorizationInfo + Specifies the CIM of the SslInfo. +#> +function Set-SslInfo +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Site, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimInstance] + $SslInfo + ) + + foreach ($value in ($SslInfo.CimInstanceProperties | Where-Object {$null -ne $_.Value}).Name) + { + $correctValue = switch($value) + { + CertificateThumbprint { 'serverCertHash' } + CertificateStoreName { 'serverCertStoreName' } + RequireSsl128 { 'ssl128' } + ControlChannelPolicy { 'controlChannelPolicy' } + DataChannelPolicy { 'dataChannelPolicy' } + } + + Set-ItemProperty -Path "IIS:\Sites\$Site" ` + -Name "ftpServer.security.ssl.$correctValue" ` + -Value ($SslInfo.CimInstanceProperties | ` + Where-Object {$_.Name -eq $value}).Value + } +} + +<# + .SYNOPSIS + Helper function used to validate that the AuthorizationInfo is unique. + + .PARAMETER Site + Specifies the name of the FTP Site. + + .PARAMETER AuthorizationInfo + Specifies the CIM of the AuthorizationInfo. +#> +function Test-AuthorizationInfo +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Site, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $AuthorizationInfo + ) + + $currentFtpAuthorizationInfo = (Get-WebConfiguration ` + -Filter '/system.ftpServer/security/authorization' ` + -Location $Site).Collection + + if ($currentFtpAuthorizationInfo.Count -ne $AuthorizationInfo.Count) + { + return $false + } + + foreach ($Authorization in $AuthorizationInfo) + { + if ($Authorization.users) + { + if(-not(Confirm-UniqueFTPAuthorization -CurrentAuthorizationCollection $currentFtpAuthorizationInfo ` + -Authorization $Authorization ` + -Property users)) + { + return $false + } + } + + if ($Authorization.roles) + { + if(-not(Confirm-UniqueFTPAuthorization -CurrentAuthorizationCollection $currentFtpAuthorizationInfo ` + -Authorization $Authorization ` + -Property roles)) + { + return $false + } + } + } + + return $true +} + +#endregion + +Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_FTP/MSFT_FTP.schema.mof b/DSCResources/MSFT_FTP/MSFT_FTP.schema.mof new file mode 100644 index 000000000..c35b22228 --- /dev/null +++ b/DSCResources/MSFT_FTP/MSFT_FTP.schema.mof @@ -0,0 +1,68 @@ +[ClassVersion("2.0.0"), FriendlyName("FTP")] +class MSFT_FTP : OMI_BaseResource +{ + [Write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] String Ensure; + [Key, Description("Specifies the name of the FTP site.")] String Name; + [Write, Description("Specifies physical location of the FTP site.")] String PhysicalPath; + [Write, Description("Specifies the username for physical path access of the FTP site.")] String PhysicalPathAccessAccount; + [Write, Description("Specifies the password for physical path access of the FTP site.")] String PhysicalPathAccessPass; + [Write,ValueMap{"Started","Stopped"},Values{"Started", "Stopped"}] String State; + [Write, Description("Specifies the name of the application pool to be used.")] String ApplicationPool; + [Write, EmbeddedInstance("MSFT_FTPAuthenticationInformation"), Description("Hashtable containing authentication information (Anonymous, Basic)")] String AuthenticationInfo; + [Write, EmbeddedInstance("MSFT_FTPAuthorizationInformation"), Description("Hashtable containing authentication information (AccessType, Roles, Permissions, Users)")] String AuthorizationInfo[]; + [Write, EmbeddedInstance("MSFT_FTPBindingInformation"), Description("Website's binding information in the form of an array of embedded instances of the MSFT_FTPBindingInformation CIM class.")] String BindingInfo[]; + [Write, EmbeddedInstance("MSFT_FTPSslInformation"), Description("Hashtable containing Ssl information (ControlChannelPolicy, DataChannelPolicy, RequireSsl128, CertificateThumbprint, CertificateStoreName)")] String SslInfo; + [Write, Description ("The external firewall IP address used for passive connections")] String FirewallIPAddress; + [Write, Description ("The starting port number in port range used for data connections in passive mode")] UInt16 StartingDataChannelPort; + [Write, Description ("The ending port number in port range used for data connections in passive mode")] UInt16 EndingDataChannelPort; + [Write, Description ("The message the FTP server displays when FTP clients have logged in to the FTP server")] String GreetingMessage; + [Write, Description ("The message the FTP server displays when FTP clients log off the FTP server")] String ExitMessage; + [Write, Description ("The message the FTP server displays when FTP clients first connect to the FTP server")] String BannerMessage; + [Write, Description ("The message when clients cannot connect because the FTP service has reached the maximum number of client connections allowed")] String MaxClientsMessage; + [Write, Description ("Whether to display the default identification banner for the FTP server")] Boolean SuppressDefaultBanner; + [Write, Description ("Whether to display detailed error messages on the local host")] Boolean AllowLocalDetailedErrors; + [Write, Description ("Whether to display a specific set of user variables in FTP messages")] Boolean ExpandVariablesInMessages; + [Write, Description ("The directory to be used for logfiles")] String LogPath; + [Write, Description ("The W3C logging fields"), ValueMap{"Date","Time","ClientIP","UserName","ServerIP","Method","UriStem","UriQuery","HttpStatus","Win32Status","TimeTaken","ServerPort","UserAgent","Referer","HttpSubStatus"},Values{"Date","Time","ClientIP","UserName","ServerIP","Method","UriStem","UriQuery","HttpStatus","Win32Status","TimeTaken","ServerPort","UserAgent","Referer","HttpSubStatus"}] String LogFlags[]; + [Write, Description ("How often the log file should rollover"), ValueMap{"Hourly","Daily","Weekly","Monthly","MaxSize"},Values{"Hourly","Daily","Weekly","Monthly","MaxSize"}] String LogPeriod; + [Write, Description ("How large the file should be before it is truncated")] String LogTruncateSize; + [Write, Description ("Use the localtime for file naming and rollover")] Boolean LoglocalTimeRollover; + [Write, Description ("What method of Directory Browsing should be enabled"), ValueMap{"StyleUnix","LongDate","DisplayAvailableBytes","DisplayVirtualDirectories"},Values{"StyleUnix","LongDate","DisplayAvailableBytes","DisplayVirtualDirectories"}] String DirectoryBrowseFlags[]; + [Write, Description ("What method of UserIsolation should be enabled"), ValueMap{"None","StartInUsersDirectory","IsolateAllDirectories","IsolateRootDirectoryOnly"},Values{"None","StartInUsersDirectory","IsolateAllDirectories","IsolateRootDirectoryOnly"}] String UserIsolation; +}; + +[ClassVersion("1.0.0")] +class MSFT_FTPSslInformation +{ + [Write,ValueMap{"SslAllow", "SslRequire", "SslRequireCredentialsOnly"},Values{"SslAllow", "SslRequire", "SslRequireCredentialsOnly"}] String ControlChannelPolicy; + [Write,ValueMap{"SslAllow", "SslRequire", "SslDeny"},Values{"SslAllow", "SslRequire", "SslDeny"}] String DataChannelPolicy; + [Write] Boolean RequireSsl128; + [Write] String CertificateThumbprint; + [Write,ValueMap{"My", "WebHosting"},Values{"My", "WebHosting"}] String CertificateStoreName; +}; + +[ClassVersion("1.0.0")] +class MSFT_FTPBindingInformation +{ + [Required,ValueMap{"ftp"},Values{"ftp"}] String Protocol; + [Write] String BindingInformation; + [Write] String IPAddress; + [Write] UInt16 Port; + [Write] String HostName; +}; + +[ClassVersion("1.0.0")] +class MSFT_FTPAuthorizationInformation +{ + [Write,ValueMap{"Allow", "Deny"},Values{"Allow", "Deny"}] String AccessType; + [Write] String Roles; + [Write,ValueMap{"Read", "Write", "Read,Write" },Values{"Read", "Write", "Read,Write"}] String Permissions; + [Write] String Users; +}; + +[ClassVersion("1.0.0")] +class MSFT_FTPAuthenticationInformation +{ + [Write] Boolean Anonymous; + [Write] Boolean Basic; +}; diff --git a/DSCResources/MSFT_FTP/en-us/MSFT_FTP.strings.psd1 b/DSCResources/MSFT_FTP/en-us/MSFT_FTP.strings.psd1 new file mode 100644 index 000000000..f1e826e23 --- /dev/null +++ b/DSCResources/MSFT_FTP/en-us/MSFT_FTP.strings.psd1 @@ -0,0 +1,69 @@ +ConvertFrom-StringData @' + ErrorftpSiteDiscoveryFailure = Failure to get the requested ftpSite "{0}" information from the target machine. + ErrorftpSiteCreationFailure = Failure to successfully create the ftpSite "{0}". Error: "{1}". + ErrorftpSiteRemovalFailure = Failure to successfully remove the ftpSite "{0}". Error: "{1}". + ErrorftpSiteStateFailure = Failure to successfully set the state of the website "{0}". Error: "{1}". + ErrorServerCertHashFailure = No such cert with the hash of "{0}" exists under store "{1}". + VerboseGetTargetAbsent = No ftpSite exists with this name. + VerboseGetTargetPresent = A single ftpSite exists with this name. + VerboseSetTargetftpSiteCreated = Successfully created ftpSite "{0}". + VerboseSetTargetftpSiteRemoved = Successfully removed ftpSite "{0}". + VerboseSetTargetAuthenticationInfoUpdated = Successfully updated AuthenticationInfo on ftpSite "{0}". + VerboseSetTargetAuthorizationInfoUpdated = Successfully updated AuthorizationInfo on ftpSite "{0}". + VerboseSetTargetUpdateAllowLocalDetailedErrors = Successfully updated AllowLocalDetailedErrors on ftpSite "{0}". + VerboseSetTargetUpdateBannerMessage = Successfully updated Banner message on ftpSite "{0}". + VerboseSetTargetUpdatedApplicationPool = Successfully updated ApplicationPool on ftpSite "{0}". + VerboseSetTargetUpdatedBindingInfo = Successfully updated BindingInfo on ftpSite "{0}". + VerboseSetTargetUpdateDirectoryBrowseFlags = Successfully updated DirectoryBrowseFlags on ftpSite "{0}". + VerboseSetTargetUpdateEndingDataChannelPort = Successfully updated ending data channel port on ftpSite "{0}". + VerboseSetTargetUpdateExitMessage = Successfully updated Exit message on ftpSite "{0}". + VerboseSetTargetUpdateExpandVariablesInMessages = Successfully updated ExpandVariablesInMessages on ftpSite "{0}". + VerboseSetTargetUpdateExternalIPaddress = Successfully updated external firewall IP address on ftpSite "{0}". + VerboseSetTargetUpdateGreetingMessage = Successfully updated Greeting message on ftpSite "{0}". + VerboseSetTargetUpdateLogFlags = Successfully updated LogFlags on ftpSite "{0}". + VerboseSetTargetUpdateLoglocalTimeRollover = Successfully updated LoglocalTimeRollover on ftpSite "{0}". + VerboseSetTargetUpdateLogPath = Successfully updated LogPath on ftpSite "{0}". + VerboseSetTargetUpdateLogPeriod = Successfully updated LogPeriod on ftpSite "{0}". + VerboseSetTargetUpdateLogTruncateSize = Successfully updated TruncateSize on ftpSite "{0}". + VerboseSetTargetUpdateMaxClientsMessage = Successfully updated MaxClients message on ftpSite "{0}". + VerboseSetTargetUpdatedPhysicalPath = Successfully updated PhysicalPath on ftpSite "{0}". + VerboseSetTargetUpdatePhysicalPathAccessPass = Successfully updated password for physical path access on ftpSite "{0}". + VerboseSetTargetUpdatePhysicalPathAccessAccount = Successfully updated username for physical path access on ftpSite "{0}". + VerboseSetTargetUpdatedState = Successfully updated State on ftpSite "{0}". + VerboseSetTargetUpdateSslInfo = Successfully updated SslInfo on ftpSite "{0}". + VerboseSetTargetUpdateStartingDataChannelPort = Successfully updated starting data channel port on ftpSite "{0}". + VerboseSetTargetUpdateSuppressDefaultBanner = Successfully updated SuppressDefaultBanner on ftpSite "{0}". + VerboseSetTargetUpdateUserIsolation = Successfully updated UserIsolation on ftpSite "{0}". + VerboseTestTargetFalseAllowLocalDetailedErrors = AllowLocalDetailedErrors for ftpSite "{0}" does not match the desired state. + VerboseTestTargetFalseApplicationPool = Application Pool for ftpSite "{0}" does not match the desired state. + VerboseTestTargetFalseAuthenticationInfo = AuthenticationInfo for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseAuthorizationInfo = AuthorizationInfo for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseBannerMessage = BannerMessage for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseBindingInfo = BindingInfo for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseDirectoryBrowseFlags = DirectoryBrowseFlags for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseEndingDataChannelPort = Ending data channel port for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseEnsure = The Ensure state for ftpSite "{0}" does not match the desired state. + VerboseTestTargetFalseExitMessage = ExitMessage for ftpSite "{0}" does not match the desired state. + VerboseTestTargetFalseExpandVariablesInMessages = ExpandVariablesInMessages for ftpSite "{0}" does not match the desired state. + VerboseTestTargetFalseExternalIPaddress = The external firewall IP address for ftpSite "{0}" does not match the desired state. + VerboseTestTargetFalseGreetingMessage = GreetingMessage for ftpSite "{0}" does not match the desired state. + VerboseTestTargetFalseLogFlags = LogFlags for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseLoglocalTimeRollover = LoglocalTimeRollover for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseLogPath = LogPath for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseLogPeriod = LogPeriod for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseLogTruncateSize = LogTruncateSize for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseMaxClientsMessage = MaxClientsMessage for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseSslInfo = SslInfo for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseStartingDataChannelPort = Starting data channel port for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalseState = The state of ftpSite "{0}" does not match the desired state. + VerboseTestTargetFalseSuppressDefaultBanner = SuppressDefaultBanner for ftpSite "{0}" is not in the desired state. + VerboseTestTargetFalsePhysicalPath = Physical Path of ftpSite "{0}" does not match the desired state. + VerboseTestTargetFalsePhysicalPathAccessPass = Password for physical path access of ftpSite "{0}" does not match the desired state. + VerboseTestTargetFalsePhysicalPathAccessAccount = Username for physical path access of ftpSite "{0}" does not match the desired state. + VerboseTestTargetFalseUserIsolation = UserIsolation for ftpSite "{0}" is not in the desired state. + VerboseTestTargetTrueResult = The target resource is already in the desired state. No action is required. + VerboseTestTargetFalseResult = The target resource is not in the desired state. + VerboseStartWebsite = Successfully started ftpSite "{0}". + VerboseStopWebsite = Successfully stopped ftpSite "{0}". + WarningLogPeriod = LogTruncateSize is specified as an input so LogPeriod input will be ignored and set to "MaxSize" on Website "{0}". +'@ diff --git a/DSCResources/MSFT_xWebApplication/MSFT_xWebApplication.psm1 b/DSCResources/MSFT_xWebApplication/MSFT_xWebApplication.psm1 index f2e989d94..6ea51bc66 100644 --- a/DSCResources/MSFT_xWebApplication/MSFT_xWebApplication.psm1 +++ b/DSCResources/MSFT_xWebApplication/MSFT_xWebApplication.psm1 @@ -6,7 +6,6 @@ data LocalizedData { # culture="en-US" ConvertFrom-StringData -StringData @' - ErrorWebApplicationTestAutoStartProviderFailure = Desired AutoStartProvider is not valid due to a conflicting Global Property. Ensure that the serviceAutoStartProvider is a unique key. VerboseGetTargetResource = Get-TargetResource has been run. VerboseSetTargetAbsent = Removing existing Web Application "{0}". VerboseSetTargetPresent = Creating new Web application "{0}". @@ -17,8 +16,8 @@ data LocalizedData VerboseSetTargetPreload = Updating Preload for Web application "{0}". VerboseSetTargetAutostart = Updating AutoStart for Web application "{0}". VerboseSetTargetIISAutoStartProviders = Updating AutoStartProviders for IIS. - VerboseSetTargetWebApplicationAutoStartProviders = Updating AutoStartProviders for Web application "{0}". - VerboseSetTargetEnabledProtocols = Updating EnabledProtocols for Web application "{0}". + VerboseSetTargetWebApplicationAutoStartProviders = Updating AutoStartProviders for Web application "{0}". + VerboseSetTargetEnabledProtocols = Updating EnabledProtocols for Web application "{0}". VerboseTestTargetFalseAbsent = Web application "{0}" is absent and should not absent. VerboseTestTargetFalsePresent = Web application $Name should be absent and is not absent. VerboseTestTargetFalsePhysicalPath = Physical path for web application "{0}" does not match desired state. @@ -27,7 +26,6 @@ data LocalizedData VerboseTestTargetFalseAuthenticationInfo = AuthenticationInfo for web application "{0}" is not in the desired state. VerboseTestTargetFalsePreload = Preload for web application "{0}" is not in the desired state. VerboseTestTargetFalseAutostart = Autostart for web application "{0}" is not in the desired state. - VerboseTestTargetFalseAutoStartProviders = AutoStartProviders for web application "{0}" are not in the desired state. VerboseTestTargetFalseIISAutoStartProviders = AutoStartProviders for IIS are not in the desired state. VerboseTestTargetFalseWebApplicationAutoStartProviders = AutoStartProviders for web application "{0}" are not in the desired state. VerboseTestTargetFalseEnabledProtocols = EnabledProtocols for web application "{0}" are not in the desired state. @@ -36,11 +34,10 @@ data LocalizedData <# .SYNOPSIS - This will return a hashtable of results + This will return a hashtable of results #> function Get-TargetResource { - [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param @@ -60,9 +57,10 @@ function Get-TargetResource Assert-Module - $webApplication = Get-WebApplication -Site $Website -Name $Name - $cimAuthentication = Get-AuthenticationInfo -Site $Website -Name $Name - $currentSslFlags = (Get-SslFlags -Location "${Website}/${Name}") + $name = Get-WebApplicationNameFixed -Name $Name + $webApplication = Get-WebApplication -Site $Website -Name $name + $CimAuthentication = Get-AuthenticationInfo -Site $Website -Application $name -IisType 'Application' + $CurrentSslFlags = (Get-SslFlags -Location "${Website}/${name}") $Ensure = 'Absent' @@ -72,10 +70,10 @@ function Get-TargetResource } Write-Verbose -Message $LocalizedData.VerboseGetTargetResource - + $returnValue = @{ Website = $Website - Name = $Name + Name = $name WebAppPool = $webApplication.applicationPool PhysicalPath = $webApplication.PhysicalPath AuthenticationInfo = $cimAuthentication @@ -91,13 +89,12 @@ function Get-TargetResource } - <# - .SYNOPSIS - This will set the desired state - #> +<# +.SYNOPSIS + This will set the desired state +#> function Set-TargetResource { - [CmdletBinding()] param ( @@ -125,16 +122,16 @@ function Set-TargetResource [Boolean] $PreloadEnabled, - + [Boolean] $ServiceAutoStartEnabled, [String] $ServiceAutoStartProvider, - + [String] $ApplicationType, - + [ValidateSet('http','https','net.tcp','net.msmq','net.pipe')] [String[]] $EnabledProtocols ) @@ -145,11 +142,11 @@ function Set-TargetResource { $webApplication = Get-WebApplication -Site $Website -Name $Name - if ($AuthenticationInfo -eq $null) + if ($null -eq $AuthenticationInfo) { - $AuthenticationInfo = Get-DefaultAuthenticationInfo + $AuthenticationInfo = Get-DefaultAuthenticationInfo -IisType 'Application' } - + if ($webApplication.count -eq 0) { Write-Verbose -Message ($LocalizedData.VerboseSetTargetPresent -f $Name) @@ -200,18 +197,18 @@ function Set-TargetResource Set-WebConfigurationProperty @params } - # Set Authentication; if not defined then pass in DefaultAuthenticationInfo - if ($PSBoundParameters.ContainsKey('AuthenticationInfo') -and ` - (-not (Test-AuthenticationInfo -Site $Website ` - -Name $Name ` - -AuthenticationInfo $AuthenticationInfo))) + # Set Authentication; if not defined then pass in Default AuthenticationInfo + if (-not (Test-AuthenticationInfo -Site $Website ` + -Application $Name ` + -IisType 'Application' ` + -AuthenticationInfo $AuthenticationInfo)) { Write-Verbose -Message ($LocalizedData.VerboseSetTargetAuthenticationInfo -f $Name) Set-AuthenticationInfo -Site $Website ` - -Name $Name ` + -Application $Name ` + -IisType 'Application' ` -AuthenticationInfo $AuthenticationInfo ` -ErrorAction Stop ` - -Verbose } # Update Preload if required @@ -257,7 +254,7 @@ function Set-TargetResource -Value $ServiceAutoStartProvider ` -ErrorAction Stop } - + # Update EnabledProtocols if required if ($PSBoundParameters.ContainsKey('EnabledProtocols') -and ` (-not(Confirm-UniqueEnabledProtocols ` @@ -279,7 +276,6 @@ function Set-TargetResource Write-Verbose -Message ($LocalizedData.VerboseSetTargetAbsent -f $Name) Remove-WebApplication -Site $Website -Name $Name } - } <# @@ -317,16 +313,16 @@ function Test-TargetResource [Boolean] $preloadEnabled, - + [Boolean] $serviceAutoStartEnabled, [String] $serviceAutoStartProvider, - + [String] $ApplicationType, - + [ValidateSet('http','https','net.tcp','net.msmq','net.pipe')] [String[]] $EnabledProtocols ) @@ -335,24 +331,24 @@ function Test-TargetResource $webApplication = Get-WebApplication -Site $Website -Name $Name - if ($AuthenticationInfo -eq $null) + if ($null -eq $AuthenticationInfo) { - $AuthenticationInfo = Get-DefaultAuthenticationInfo + $AuthenticationInfo = Get-DefaultAuthenticationInfo -IisType 'Application' } - if ($webApplication.count -eq 0 -and $Ensure -eq 'Present') + if ($webApplication.count -eq 0 -and $Ensure -eq 'Present') { Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseAbsent -f $Name) return $false } - if ($webApplication.count -eq 1 -and $Ensure -eq 'Absent') + if ($webApplication.count -eq 1 -and $Ensure -eq 'Absent') { Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalsePresent -f $Name) return $false } - if ($webApplication.count -eq 1 -and $Ensure -eq 'Present') + if ($webApplication.count -eq 1 -and $Ensure -eq 'Present') { #Check Physical Path if ($webApplication.physicalPath -ne $PhysicalPath) @@ -377,13 +373,12 @@ function Test-TargetResource } #Check AuthenticationInfo - if ($PSBoundParameters.ContainsKey('AuthenticationInfo') -and ` - (-not (Test-AuthenticationInfo -Site $Website ` - -Name $Name ` - -AuthenticationInfo $AuthenticationInfo))) + if (-not (Test-AuthenticationInfo -Site $Website ` + -Application $Name ` + -IisType 'Application' ` + -AuthenticationInfo $AuthenticationInfo)) { - Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseAuthenticationInfo ` - -f $Name) + Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseAuthenticationInfo -f $Name) return $false } @@ -393,7 +388,7 @@ function Test-TargetResource { Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalsePreload -f $Name) return $false - } + } #Check AutoStartEnabled if($PSBoundParameters.ContainsKey('ServiceAutoStartEnabled') -and ` @@ -403,7 +398,7 @@ function Test-TargetResource return $false } - #Check AutoStartProviders + #Check AutoStartProviders if ($PSBoundParameters.ContainsKey('ServiceAutoStartProvider') -and ` $webApplication.serviceAutoStartProvider -ne $ServiceAutoStartProvider) { @@ -412,14 +407,14 @@ function Test-TargetResource -ApplicationType $ApplicationType)) { Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseIISAutoStartProviders) - return $false + return $false } Write-Verbose -Message ` ($LocalizedData.VerboseTestTargetFalseWebApplicationAutoStartProviders -f $Name) - return $false + return $false } - - # Update EnabledProtocols if required + + #Update EnabledProtocols if required if ($PSBoundParameters.ContainsKey('EnabledProtocols') -and ` (-not(Confirm-UniqueEnabledProtocols ` -ExistingProtocols $webApplication.EnabledProtocols ` @@ -431,9 +426,9 @@ function Test-TargetResource } } - + return $true - + } <# @@ -445,7 +440,7 @@ function Test-TargetResource .PARAMETER ProposedProtocols Specifies desired SMTP bindings. .NOTES - ExistingProtocols is a String whereas ProposedProtocols is an array of Strings + ExistingProtocols is a String whereas ProposedProtocols is an array of Strings so we need to do some extra work in comparing them #> function Confirm-UniqueEnabledProtocols @@ -453,18 +448,18 @@ function Confirm-UniqueEnabledProtocols [CmdletBinding()] [OutputType([Boolean])] param - ( + ( [Parameter(Mandatory = $true)] [AllowEmptyString()] [String] $ExistingProtocols, - + [Parameter(Mandatory = $true)] [String[]] $ProposedProtocols ) $inputToCheck = @() foreach ($proposedProtocol in $ProposedProtocols) - { + { $inputToCheck += $proposedProtocol } @@ -492,118 +487,6 @@ function Confirm-UniqueEnabledProtocols #region Helper Functions -<# -.SYNOPSIS - Helper function used to validate that the AutoStartProviders is unique to other - websites. Returns False if the AutoStartProviders exist. -.PARAMETER serviceAutoStartProvider - Specifies the name of the AutoStartProviders. -.PARAMETER ExcludeStopped - Specifies the name of the Application Type for the AutoStartProvider. -.NOTES - This tests for the existance of a AutoStartProviders which is globally assigned. - As AutoStartProviders need to be uniquely named it will check for this and error out if - attempting to add a duplicatly named AutoStartProvider. - Name is passed in to bubble to any error messages during the test. -#> -function Confirm-UniqueServiceAutoStartProviders -{ - [CmdletBinding()] - [OutputType([Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [String] $ServiceAutoStartProvider, - - [Parameter(Mandatory = $true)] - [String] $ApplicationType - ) - - $WebSiteAutoStartProviders = (Get-WebConfiguration ` - -filter /system.applicationHost/serviceAutoStartProviders).Collection - - $ExistingObject = $WebSiteAutoStartProviders | ` - Where-Object -Property Name -eq -Value $serviceAutoStartProvider | ` - Select-Object Name,Type - - $ProposedObject = @(New-Object -TypeName PSObject -Property @{ - name = $ServiceAutoStartProvider - type = $ApplicationType - }) - - if(-not $ExistingObject) - { - return $false - } - - if(-not (Compare-Object -ReferenceObject $ExistingObject ` - -DifferenceObject $ProposedObject ` - -Property name)) - { - if(Compare-Object -ReferenceObject $ExistingObject ` - -DifferenceObject $ProposedObject ` - -Property type) - { - $ErrorMessage = $LocalizedData.ErrorWebApplicationTestAutoStartProviderFailure - New-TerminatingError ` - -ErrorId 'ErrorWebApplicationTestAutoStartProviderFailure' ` - -ErrorMessage $ErrorMessage ` - -ErrorCategory 'InvalidResult' - } - } - - return $true - -} - -<# -.SYNOPSIS - Helper function used to validate that the authenticationProperties for an Application. -.PARAMETER Site - Specifies the name of the Website. -.PARAMETER Name - Specifies the name of the Application. -#> -function Get-AuthenticationInfo -{ - [CmdletBinding()] - [OutputType([Microsoft.Management.Infrastructure.CimInstance])] - param - ( - [Parameter(Mandatory = $true)] - [String] $Site, - - [Parameter(Mandatory = $true)] - [String] $Name - ) - - $authenticationProperties = @{} - foreach ($type in @('Anonymous', 'Basic', 'Digest', 'Windows')) - { - $authenticationProperties[$type] = [Boolean](Test-AuthenticationEnabled -Site $Site ` - -Name $Name ` - -Type $type) - } - - return New-CimInstance ` - -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly -Property $authenticationProperties ` - -NameSpace 'root\microsoft\windows\desiredstateconfiguration' - -} - -<# -.SYNOPSIS - Helper function used to build a default CimInstance for AuthenticationInformation -#> -function Get-DefaultAuthenticationInfo -{ - New-CimInstance -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly ` - -Property @{Anonymous=$false;Basic=$false;Digest=$false;Windows=$false} ` - -NameSpace 'root\microsoft\windows\desiredstateconfiguration' -} - <# .SYNOPSIS Helper function used to return the SSLFlags on an Application. @@ -626,7 +509,7 @@ function Get-SslFlags -Filter 'system.webserver/security/access' | ` ForEach-Object { $_.sslFlags } - if ($null -eq $SslFlags) + if ($null -eq $SslFlags) { return [String]::Empty } @@ -636,170 +519,7 @@ function Get-SslFlags <# .SYNOPSIS - Helper function used to set authenticationProperties for an Application. -.PARAMETER Site - Specifies the name of the Website. -.PARAMETER Name - Specifies the name of the Application. -.PARAMETER Type - Specifies the type of Authentication, -Limited to the set: ('Anonymous','Basic','Digest','Windows'). -.PARAMETER Enabled - Whether the Authentication is enabled or not. -#> -function Set-Authentication -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [String] $Site, - - [Parameter(Mandatory = $true)] - [String] $Name, - - [Parameter(Mandatory = $true)] - [ValidateSet('Anonymous','Basic','Digest','Windows')] - [String] $Type, - - [Boolean] $Enabled - ) - - Set-WebConfigurationProperty ` - -Filter /system.WebServer/security/authentication/${Type}Authentication ` - -Name enabled ` - -Value $Enabled ` - -Location "${Site}/${Name}" -} - -<# -.SYNOPSIS - Helper function used to validate that the authenticationProperties for an Application. -.PARAMETER Site - Specifies the name of the Website. -.PARAMETER Name - Specifies the name of the Application. -.PARAMETER AuthenticationInfo - A CimInstance of what state the AuthenticationInfo should be. -#> -function Set-AuthenticationInfo -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [String] $Site, - - [Parameter(Mandatory = $true)] - [String] $Name, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [Microsoft.Management.Infrastructure.CimInstance] $AuthenticationInfo - ) - - foreach ($type in @('Anonymous', 'Basic', 'Digest', 'Windows')) - { - $enabled = ($AuthenticationInfo.CimInstanceProperties[$type].Value -eq $true) - Set-Authentication -Site $Site ` - -Name $Name ` - -Type $type ` - -Enabled $enabled - } -} - -<# -.SYNOPSIS - Helper function used to test the authenticationProperties state for an Application. - Will return that value which will either [String]True or [String]False -.PARAMETER Site - Specifies the name of the Website. -.PARAMETER Name - Specifies the name of the Application. -.PARAMETER Type - Specifies the type of Authentication, - limited to the set: ('Anonymous','Basic','Digest','Windows'). -#> - -function Test-AuthenticationEnabled -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [String] $Site, - - [Parameter(Mandatory = $true)] - [String] $Name, - - [Parameter(Mandatory = $true)] - [ValidateSet('Anonymous','Basic','Digest','Windows')] - [String] $Type - ) - - - $prop = Get-WebConfigurationProperty ` - -Filter /system.WebServer/security/authentication/${Type}Authentication ` - -Name enabled ` - -Location "${Site}/${Name}" - - return $prop.Value - -} - -<# -.SYNOPSIS - Helper function used to test the authenticationProperties state for an Application. - Will return that result which will either [boolean]$True or [boolean]$False for use in - Test-TargetResource. - Uses Test-AuthenticationEnabled to determine this. First incorrect result will break - this function out. -.PARAMETER Site - Specifies the name of the Website. -.PARAMETER Name - Specifies the name of the Application. -.PARAMETER AuthenticationInfo - A CimInstance of what state the AuthenticationInfo should be. -#> - -function Test-AuthenticationInfo -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [String] $Site, - - [Parameter(Mandatory = $true)] - [String] $Name, - - [Parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] - [Microsoft.Management.Infrastructure.CimInstance] $AuthenticationInfo - ) - - foreach ($type in @('Anonymous', 'Basic', 'Digest', 'Windows')) - { - - $expected = $AuthenticationInfo.CimInstanceProperties[$type].Value - $actual = Test-AuthenticationEnabled -Site $Site ` - -Name $Name ` - -Type $type - if ($expected -ne $actual) - { - return $false - } - } - - return $true - -} - -<# -.SYNOPSIS - Helper function used to test the SSLFlags on an Application. + Helper function used to test the SSLFlags on an Application. Will return $true if they match and $false if they do not. .PARAMETER SslFlags Specifies the SslFlags to Test @@ -831,6 +551,27 @@ function Test-SslFlags return $true } +<# +.SYNOPSIS + Helper function to replace a backslash with a forward slash in + the web app names. +.PARAMETER Name + The web application name +.NOTES + Backslash is replaced by IIS with a forward slash, for compatibility we do the same. +#> +function Get-WebApplicationNameFixed +{ + [CmdletBinding()] + [OutputType([string])] + param( + [parameter(Mandatory = $true)] + [System.String] $Name + ) + + $Name -replace '\\', '/' +} + #endregion Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xWebsite/MSFT_xWebsite.psm1 b/DSCResources/MSFT_xWebsite/MSFT_xWebsite.psm1 index 7f6ac410c..f0e412e0a 100644 --- a/DSCResources/MSFT_xWebsite/MSFT_xWebsite.psm1 +++ b/DSCResources/MSFT_xWebsite/MSFT_xWebsite.psm1 @@ -8,98 +8,71 @@ data LocalizedData { # culture="en-US" ConvertFrom-StringData -StringData @' - ErrorWebsiteNotFound = The requested website "{0}" cannot be found on the target machine. - ErrorWebsiteDiscoveryFailure = Failure to get the requested website "{0}" information from the target machine. - ErrorWebsiteCreationFailure = Failure to successfully create the website "{0}". Error: "{1}". - ErrorWebsiteRemovalFailure = Failure to successfully remove the website "{0}". Error: "{1}". - ErrorWebsiteBindingUpdateFailure = Failure to successfully update the bindings for website "{0}". Error: "{1}". - ErrorWebsiteBindingInputInvalidation = Desired website bindings are not valid for website "{0}". - ErrorWebsiteCompareFailure = Failure to successfully compare properties for website "{0}". Error: "{1}". - ErrorWebBindingCertificate = Failure to add certificate to web binding. Please make sure that the certificate thumbprint "{0}" is valid. Error: "{1}". - ErrorWebsiteStateFailure = Failure to successfully set the state of the website "{0}". Error: "{1}". - ErrorWebsiteBindingConflictOnStart = Website "{0}" could not be started due to binding conflict. Ensure that the binding information for this website does not conflict with any existing website's bindings before trying to start it. - ErrorWebBindingInvalidIPAddress = Failure to validate the IPAddress property value "{0}". Error: "{1}". - ErrorWebBindingInvalidPort = Failure to validate the Port property value "{0}". The port number must be a positive integer between 1 and 65535. - ErrorWebBindingMissingBindingInformation = The BindingInformation property is required for bindings of type "{0}". - ErrorWebBindingMissingCertificateThumbprint = The CertificateThumbprint property is required for bindings of type "{0}". - ErrorWebBindingMissingSniHostName = The HostName property is required for use with Server Name Indication. - ErrorWebBindingInvalidCertificateSubject = The Subject "{0}" provided is not found on this host in store "{1}" - ErrorWebsitePreloadFailure = Failure to set Preload on Website "{0}". Error: "{1}". - ErrorWebsiteAutoStartFailure = Failure to set AutoStart on Website "{0}". Error: "{1}". - ErrorWebsiteAutoStartProviderFailure = Failure to set AutoStartProvider on Website "{0}". Error: "{1}". - ErrorWebsiteTestAutoStartProviderFailure = Desired AutoStartProvider is not valid due to a conflicting Global Property. Ensure that the serviceAutoStartProvider is a unique key." - VerboseSetTargetUpdatedSiteId = Site Id for website "{0}" has been updated to "{1}". - VerboseSetTargetUpdatedPhysicalPath = Physical Path for website "{0}" has been updated to "{1}". - VerboseGetTargetAbsent = No Website exists with this name. - VerboseGetTargetPresent = A single Website exists with this name - VerboseSetTargetUpdatedApplicationPool = Application Pool for website "{0}" has been updated to "{1}". - VerboseSetTargetUpdatedBindingInfo = Bindings for website "{0}" have been updated. - VerboseSetTargetUpdatedEnabledProtocols = Enabled Protocols for website "{0}" have been updated to "{1}". - VerboseSetTargetUpdatedState = State for website "{0}" has been updated to "{1}". - VerboseSetTargetWebsiteCreated = Successfully created website "{0}". - VerboseSetTargetWebsiteStarted = Successfully started website "{0}". - VerboseSetTargetWebsiteRemoved = Successfully removed website "{0}". - VerboseSetTargetAuthenticationInfoUpdated = Successfully updated AuthenticationInfo on website "{0}". - VerboseSetTargetWebsitePreloadUpdated = Successfully updated Preload on website "{0}". - VerboseSetTargetWebsiteAutoStartUpdated = Successfully updated AutoStart on website "{0}". + ErrorWebsiteDiscoveryFailure = Failure to get the requested website "{0}" information from the target machine. + ErrorWebsiteCreationFailure = Failure to successfully create the website "{0}". Error: "{1}". + ErrorWebsiteRemovalFailure = Failure to successfully remove the website "{0}". Error: "{1}". + ErrorWebsiteStateFailure = Failure to successfully set the state of the website "{0}". Error: "{1}". + ErrorWebsiteBindingConflictOnStart = Website "{0}" could not be started due to binding conflict. Ensure that the binding information for this website does not conflict with any existing website's bindings before trying to start it. + VerboseSetTargetUpdatedSiteId = Site Id for website "{0}" has been updated to "{1}". + VerboseSetTargetUpdatedPhysicalPath = Physical Path for website "{0}" has been updated to "{1}". + VerboseGetTargetAbsent = No Website exists with this name. + VerboseGetTargetPresent = A single Website exists with this name + VerboseSetTargetUpdatedApplicationPool = Application Pool for website "{0}" has been updated to "{1}". + VerboseSetTargetUpdatedBindingInfo = Bindings for website "{0}" have been updated. + VerboseSetTargetUpdatedEnabledProtocols = Enabled Protocols for website "{0}" have been updated to "{1}". + VerboseSetTargetUpdatedState = State for website "{0}" has been updated to "{1}". + VerboseSetTargetWebsiteCreated = Successfully created website "{0}". + VerboseSetTargetWebsiteStarted = Successfully started website "{0}". + VerboseSetTargetWebsiteRemoved = Successfully removed website "{0}". + VerboseSetTargetAuthenticationInfoUpdated = Successfully updated AuthenticationInfo on website "{0}". + VerboseSetTargetWebsitePreloadUpdated = Successfully updated Preload on website "{0}". + VerboseSetTargetWebsiteAutoStartUpdated = Successfully updated AutoStart on website "{0}". VerboseSetTargetWebsiteAutoStartProviderUpdated = Successfully updated AutoStartProvider on website "{0}". - VerboseSetTargetIISAutoStartProviderUpdated = Successfully updated AutoStartProvider in IIS. - VerboseSetTargetUpdateLogPath = LogPath does not match and will be updated on Website "{0}". - VerboseSetTargetUpdateLogFlags = LogFlags do not match and will be updated on Website "{0}". - VerboseSetTargetUpdateLogPeriod = LogPeriod does not match and will be updated on Website "{0}". - VerboseSetTargetUpdateLogTruncateSize = TruncateSize does not match and will be updated on Website "{0}". - VerboseSetTargetUpdateLoglocalTimeRollover = LoglocalTimeRollover does not match and will be updated on Website "{0}". - VerboseSetTargetUpdateLogFormat = LogFormat is not in the desired state and will be updated on Website "{0}" - VerboseSetTargetUpdateLogTargetW3C = LogTargetW3C is not in the desired state and will be updated on Website "{0}". - VerboseSetTargetUpdateLogCustomFields = LogCustomFields is not in the desired state and will be updated on Website "{0}" - VerboseTestTargetFalseEnsure = The Ensure state for website "{0}" does not match the desired state. - VerboseTestTargetFalseSiteId = Site Id of website "{0}" does not match the desired state. - VerboseTestTargetFalsePhysicalPath = Physical Path of website "{0}" does not match the desired state. - VerboseTestTargetFalseState = The state of website "{0}" does not match the desired state. - VerboseTestTargetFalseApplicationPool = Application Pool for website "{0}" does not match the desired state. - VerboseTestTargetFalseBindingInfo = Bindings for website "{0}" do not match the desired state. - VerboseTestTargetFalseEnabledProtocols = Enabled Protocols for website "{0}" do not match the desired state. - VerboseTestTargetFalseDefaultPage = Default Page for website "{0}" does not match the desired state. - VerboseTestTargetTrueResult = The target resource is already in the desired state. No action is required. - VerboseTestTargetFalseResult = The target resource is not in the desired state. - VerboseTestTargetFalsePreload = Preload for website "{0}" do not match the desired state. - VerboseTestTargetFalseAutoStart = AutoStart for website "{0}" do not match the desired state. - VerboseTestTargetFalseAuthenticationInfo = AuthenticationInfo for website "{0}" is not in the desired state. - VerboseTestTargetFalseIISAutoStartProvider = AutoStartProvider for IIS is not in the desired state - VerboseTestTargetFalseWebsiteAutoStartProvider = AutoStartProvider for website "{0}" is not in the desired state - VerboseTestTargetFalseLogPath = LogPath does not match desired state on Website "{0}". - VerboseTestTargetFalseLogFlags = LogFlags does not match desired state on Website "{0}". - VerboseTestTargetFalseLogPeriod = LogPeriod does not match desired state on Website "{0}". - VerboseTestTargetFalseLogTruncateSize = LogTruncateSize does not match desired state on Website "{0}". - VerboseTestTargetFalseLoglocalTimeRollover = LoglocalTimeRollover does not match desired state on Website "{0}". - VerboseTestTargetFalseLogFormat = LogFormat does not match desired state on Website "{0}". - VerboseTestTargetFalseLogTargetW3C = LogTargetW3C does not match desired state on Website "{0}". - VerboseTestTargetFalseLogCustomFields = LogCustomFields does not match desired state on Website "{0}". - VerboseConvertToWebBindingIgnoreBindingInformation = BindingInformation is ignored for bindings of type "{0}" in case at least one of the following properties is specified: IPAddress, Port, HostName. - VerboseConvertToWebBindingDefaultPort = Port is not specified. The default "{0}" port "{1}" will be used. - VerboseConvertToWebBindingDefaultCertificateStoreName = CertificateStoreName is not specified. The default value "{0}" will be used. - VerboseTestBindingInfoSameIPAddressPortHostName = BindingInfo contains multiple items with the same IPAddress, Port, and HostName combination. - VerboseTestBindingInfoSamePortDifferentProtocol = BindingInfo contains items that share the same Port but have different Protocols. - VerboseTestBindingInfoSameProtocolBindingInformation = BindingInfo contains multiple items with the same Protocol and BindingInformation combination. - VerboseTestBindingInfoInvalidCatch = Unable to validate BindingInfo: "{0}". - VerboseUpdateDefaultPageUpdated = Default page for website "{0}" has been updated to "{1}". - WarningLogPeriod = LogTruncateSize has is an input as will overwrite this desired state on Website "{0}". - WarningIncorrectLogFormat = LogFormat is not W3C, as a result LogFlags will not be used on Website "{0}". + VerboseSetTargetIISAutoStartProviderUpdated = Successfully updated AutoStartProvider in IIS. + VerboseSetTargetUpdateLogPath = LogPath does not match and will be updated on Website "{0}". + VerboseSetTargetUpdateLogFlags = LogFlags do not match and will be updated on Website "{0}". + VerboseSetTargetUpdateLogPeriod = LogPeriod does not match and will be updated on Website "{0}". + VerboseSetTargetUpdateLogTruncateSize = TruncateSize does not match and will be updated on Website "{0}". + VerboseSetTargetUpdateLoglocalTimeRollover = LoglocalTimeRollover does not match and will be updated on Website "{0}". + VerboseSetTargetUpdateLogFormat = LogFormat is not in the desired state and will be updated on Website "{0}" + VerboseSetTargetUpdateLogTargetW3C = LogTargetW3C is not in the desired state and will be updated on Website "{0}". + VerboseSetTargetUpdateLogCustomFields = LogCustomFields is not in the desired state and will be updated on Website "{0}" + VerboseTestTargetFalseEnsure = The Ensure state for website "{0}" does not match the desired state. + VerboseTestTargetFalseSiteId = Site Id of website "{0}" does not match the desired state. + VerboseTestTargetFalsePhysicalPath = Physical Path of website "{0}" does not match the desired state. + VerboseTestTargetFalseState = The state of website "{0}" does not match the desired state. + VerboseTestTargetFalseApplicationPool = Application Pool for website "{0}" does not match the desired state. + VerboseTestTargetFalseBindingInfo = Bindings for website "{0}" do not match the desired state. + VerboseTestTargetFalseEnabledProtocols = Enabled Protocols for website "{0}" do not match the desired state. + VerboseTestTargetFalseDefaultPage = Default Page for website "{0}" does not match the desired state. + VerboseTestTargetTrueResult = The target resource is already in the desired state. No action is required. + VerboseTestTargetFalseResult = The target resource is not in the desired state. + VerboseTestTargetFalsePreload = Preload for website "{0}" do not match the desired state. + VerboseTestTargetFalseAutoStart = AutoStart for website "{0}" do not match the desired state. + VerboseTestTargetFalseAuthenticationInfo = AuthenticationInfo for website "{0}" is not in the desired state. + VerboseTestTargetFalseLogPath = LogPath does not match desired state on Website "{0}". + VerboseTestTargetFalseLogFlags = LogFlags does not match desired state on Website "{0}". + VerboseTestTargetFalseLogPeriod = LogPeriod does not match desired state on Website "{0}". + VerboseTestTargetFalseLogTruncateSize = LogTruncateSize does not match desired state on Website "{0}". + VerboseTestTargetFalseLoglocalTimeRollover = LoglocalTimeRollover does not match desired state on Website "{0}". + VerboseTestTargetFalseLogFormat = LogFormat does not match desired state on Website "{0}". + VerboseTestTargetFalseLogTargetW3C = LogTargetW3C does not match desired state on Website "{0}". + WarningLogPeriod = LogTruncateSize has is an input as will overwrite this desired state on Website "{0}". + WarningIncorrectLogFormat = LogFormat is not W3C, as a result LogFlags will not be used on Website "{0}". '@ } <# - .SYNOPSYS - The Get-TargetResource cmdlet is used to fetch the status of role or Website on - the target machine. It gives the Website info of the requested role/feature on the - target machine. +.SYNOPSIS + The Get-TargetResource cmdlet is used to fetch the status of role or Website on + the target machine. It gives the Website info of the requested role/feature on the + target machine. - .PARAMETER Name - Name of the website +.PARAMETER Name + Name of the website #> function Get-TargetResource { - [CmdletBinding()] [OutputType([Hashtable])] param @@ -130,7 +103,7 @@ function Get-TargetResource Get-WebConfiguration -Filter '/system.webServer/defaultDocument/files/*' -PSPath "IIS:\Sites\$Name" | ForEach-Object -Process {Write-Output -InputObject $_.value} ) - $cimAuthentication = Get-AuthenticationInfo -Site $Name + $cimAuthentication = Get-AuthenticationInfo -Site $Name -IisType 'Website' $websiteAutoStartProviders = (Get-WebConfiguration ` -filter /system.applicationHost/serviceAutoStartProviders).Collection $webConfiguration = $websiteAutoStartProviders | ` @@ -176,15 +149,15 @@ function Get-TargetResource } <# - .SYNOPSYS - The Set-TargetResource cmdlet is used to create, delete or configure a website on the - target machine. +.SYNOPSIS + The Set-TargetResource cmdlet is used to create, delete or configure a website on the + target machine. - .PARAMETER SiteId - Optional. Specifies the IIS site Id for the web site. +.PARAMETER SiteId + Optional. Specifies the IIS site Id for the web site. - .PARAMETER PhysicalPath - Specifies the physical path of the web site. Don't set this if the site will be deployed by an external tool that updates the path. +.PARAMETER PhysicalPath + Specifies the physical path of the web site. Don't set this if the site will be deployed by an external tool that updates the path. #> function Set-TargetResource { @@ -277,6 +250,11 @@ function Set-TargetResource $website = Get-Website | Where-Object -FilterScript {$_.Name -eq $Name} + if ($null -eq $AuthenticationInfo) + { + $AuthenticationInfo = Get-DefaultAuthenticationInfo -IisType 'Website' + } + if ($Ensure -eq 'Present') { if ($null -ne $website) @@ -418,7 +396,8 @@ function Set-TargetResource } # New-WebSite has Id parameter instead of SiteId, so it's getting mapped to Id - if ($PSBoundParameters.ContainsKey('SiteId')) { + if ($PSBoundParameters.ContainsKey('SiteId')) + { $newWebsiteSplat.Add('Id', $SiteId) } elseif (-not (Get-WebSite)) { # If there are no other websites and SiteId is missing, specify the Id Parameter for the new website. @@ -426,7 +405,8 @@ function Set-TargetResource $newWebsiteSplat.Add('Id', 1) } - if ([String]::IsNullOrEmpty($PhysicalPath)) { + if ([String]::IsNullOrEmpty($PhysicalPath)) + { # If no physical path is provided run New-Website with -Force flag $website = New-Website @newWebsiteSplat -ErrorAction Stop -Force } else { @@ -515,11 +495,12 @@ function Set-TargetResource } # Set Authentication; if not defined then pass in DefaultAuthenticationInfo - if ($PSBoundParameters.ContainsKey('AuthenticationInfo') -and ` - (-not (Test-AuthenticationInfo -Site $Name ` - -AuthenticationInfo $AuthenticationInfo))) + if (-not (Test-AuthenticationInfo -Site $Name ` + -IisType 'Website' ` + -AuthenticationInfo $AuthenticationInfo)) { Set-AuthenticationInfo -Site $Name ` + -IisType 'Website' ` -AuthenticationInfo $AuthenticationInfo ` -ErrorAction Stop Write-Verbose -Message ($LocalizedData.VerboseSetTargetAuthenticationInfoUpdated ` @@ -696,12 +677,12 @@ function Set-TargetResource } <# - .SYNOPSIS - The Test-TargetResource cmdlet is used to validate if the role or feature is in a state as - expected in the instance document. +.SYNOPSIS + The Test-TargetResource cmdlet is used to validate if the role or feature is in a state as + expected in the instance document. - .PARAMETER SiteId - Optional. Specifies the IIS site Id for the web site. +.PARAMETER SiteId + Optional. Specifies the IIS site Id for the web site. #> function Test-TargetResource @@ -797,6 +778,11 @@ function Test-TargetResource $website = Get-Website | Where-Object -FilterScript {$_.Name -eq $Name} + if ($null -eq $AuthenticationInfo) + { + $AuthenticationInfo = Get-DefaultAuthenticationInfo -IisType 'Website' + } + # Check Ensure if (($Ensure -eq 'Present' -and $null -eq $website) -or ` ($Ensure -eq 'Absent' -and $null -ne $website)) @@ -886,9 +872,9 @@ function Test-TargetResource } #Check AuthenticationInfo - if ($PSBoundParameters.ContainsKey('AuthenticationInfo') -and ` - (-not (Test-AuthenticationInfo -Site $Name ` - -AuthenticationInfo $AuthenticationInfo))) + if (-not (Test-AuthenticationInfo -Site $Name ` + -IisType 'Website' ` + -AuthenticationInfo $AuthenticationInfo)) { $inDesiredState = $false Write-Verbose -Message ($LocalizedData.VerboseTestTargetFalseAuthenticationInfo) @@ -1035,153 +1021,101 @@ function Test-TargetResource return $inDesiredState } -#region Helper Functions +#region DefaultPage functions <# - .SYNOPSIS - Helper function used to validate that the logflags status. - Returns False if the loglfags do not match and true if they do - - .PARAMETER LogFlags - Specifies flags to check - - .PARAMETER Name - Specifies website to check the flags on +.SYNOPSIS + Helper function used to update default pages of website. #> -function Compare-LogFlags +function Update-DefaultPage { [CmdletBinding()] - [OutputType([Boolean])] param ( [Parameter(Mandatory = $true)] - [String[]] - [ValidateSet('Date','Time','ClientIP','UserName','SiteName','ComputerName','ServerIP','Method','UriStem','UriQuery','HttpStatus','Win32Status','BytesSent','BytesRecv','TimeTaken','ServerPort','UserAgent','Cookie','Referer','ProtocolVersion','Host','HttpSubStatus')] - $LogFlags, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] [String] - $Name + $Name, + [Parameter(Mandatory = $true)] + [String[]] + $DefaultPage ) - $currentLogFlags = (Get-Website -Name $Name).logfile.logExtFileFlags -split ',' | Sort-Object - $proposedLogFlags = $LogFlags -split ',' | Sort-Object + $allDefaultPages = @( + Get-WebConfiguration -Filter '/system.webServer/defaultDocument/files/*' ` + -PSPath "IIS:\Sites\$Name" | + ForEach-Object -Process { Write-Output -InputObject $_.value } + ) - if (Compare-Object -ReferenceObject $currentLogFlags -DifferenceObject $proposedLogFlags) + foreach ($page in $DefaultPage) { - return $false + if ($allDefaultPages -inotcontains $page) + { + Add-WebConfiguration -Filter '/system.webServer/defaultDocument/files' ` + -PSPath "IIS:\Sites\$Name" ` + -Value @{ value = $page } + Write-Verbose -Message ($LocalizedData.VerboseUpdateDefaultPageUpdated ` + -f $Name, $page) + } } - - return $true - } +#endregion -<# - .SYNOPSIS - Helper function used to validate that the website's binding information is unique to other - websites. Returns False if at least one of the bindings is already assigned to another - website. +#region Log functions - .PARAMETER Name - Specifies the name of the website. +<# +.SYNOPSIS + Helper function used to set the LogCustomField for a website. - .PARAMETER ExcludeStopped - Omits stopped websites. +.PARAMETER Site + Specifies the name of the Website. - .NOTES - This function tests standard ('http' and 'https') bindings only. - It is technically possible to assign identical non-standard bindings (such as 'net.tcp') - to different websites. +.PARAMETER LogCustomField + A CimInstance collection of what the LogCustomField should be. #> -function Confirm-UniqueBinding +function Set-LogCustomField { [CmdletBinding()] - [OutputType([Boolean])] param ( [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] [String] - $Name, - - [Parameter(Mandatory = $false)] - [Switch] - $ExcludeStopped - ) - - $website = Get-Website | Where-Object -FilterScript { $_.Name -eq $Name } - - if (-not $website) - { - $errorMessage = $LocalizedData.ErrorWebsiteNotFound ` - -f $Name - New-TerminatingError -ErrorId 'WebsiteNotFound' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidResult' - } + $Site, - $referenceObject = @( - $website.bindings.Collection | - Where-Object -FilterScript { $_.protocol -in @('http', 'https') } | - ConvertTo-WebBinding -Verbose:$false + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $LogCustomField ) - if ($ExcludeStopped) - { - $otherWebsiteFilter = { $_.Name -ne $website.Name -and $_.State -ne 'Stopped' } - } - else + $setCustomFields = @() + foreach ($customField in $LogCustomField) { - $otherWebsiteFilter = { $_.Name -ne $website.Name } - } - - $differenceObject = @( - Get-Website | - Where-Object -FilterScript $otherWebsiteFilter | - ForEach-Object -Process { $_.bindings.Collection } | - Where-Object -FilterScript { $_.protocol -in @('http', 'https') } | - ConvertTo-WebBinding -Verbose:$false - ) - - # Assume that bindings are unique - $result = $true - - $compareSplat = @{ - ReferenceObject = $referenceObject - DifferenceObject = $differenceObject - Property = @('protocol', 'bindingInformation') - ExcludeDifferent = $true - IncludeEqual = $true + $setCustomFields += @{ + logFieldName = $customField.LogFieldName + sourceName = $customField.SourceName + sourceType = $customField.SourceType + } } - if (Compare-Object @compareSplat) + # The second Set-WebConfigurationProperty is to handle an edge case where logfile.customFields is not updated correctly. May be caused by a possible bug in the IIS provider + for ($i = 1; $i -le 2; $i++) { - $result = $false + Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter "system.applicationHost/sites/site[@name='$Site']/logFile/customFields" -Name "." -Value $setCustomFields } - - return $result } <# - .SYNOPSIS - Helper function used to validate that the AutoStartProviders is unique to other websites. - returns False if the AutoStartProviders exist. - - .PARAMETER ServiceAutoStartProvider - Specifies the name of the AutoStartProviders. +.SYNOPSIS + Helper function used to test the LogCustomField state for a website. - .PARAMETER ApplicationType - Specifies the name of the Application Type for the AutoStartProvider. +.PARAMETER Site + Specifies the name of the Website. - .NOTES - This tests for the existance of a AutoStartProviders which is globally assigned. - As AutoStartProviders need to be uniquely named it will check for this and error out if - attempting to add a duplicatly named AutoStartProvider. - Name is passed in to bubble to any error messages during the test. +.PARAMETER LogCustomField + A CimInstance collection of what state the LogCustomField should be. #> -function Confirm-UniqueServiceAutoStartProviders +function Test-LogCustomField { [CmdletBinding()] [OutputType([Boolean])] @@ -1189,1051 +1123,38 @@ function Confirm-UniqueServiceAutoStartProviders ( [Parameter(Mandatory = $true)] [String] - $ServiceAutoStartProvider, + $Site, - [Parameter(Mandatory = $true)] - [String] - $ApplicationType + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $LogCustomField ) - $websiteASP = (Get-WebConfiguration ` - -filter /system.applicationHost/serviceAutoStartProviders).Collection - - $existingObject = $websiteASP | ` - Where-Object -Property Name -eq -Value $ServiceAutoStartProvider | ` - Select-Object Name,Type - - $proposedObject = @(New-Object -TypeName PSObject -Property @{ - name = $ServiceAutoStartProvider - type = $ApplicationType - }) - - if(-not $existingObject) - { - return $false - } - - if(-not (Compare-Object -ReferenceObject $existingObject ` - -DifferenceObject $proposedObject ` - -Property name)) - { - if(Compare-Object -ReferenceObject $existingObject ` - -DifferenceObject $proposedObject ` - -Property type) - { - $errorMessage = $LocalizedData.ErrorWebsiteTestAutoStartProviderFailure - New-TerminatingError -ErrorId 'ErrorWebsiteTestAutoStartProviderFailure' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidResult'` - } - } - - return $true - -} - -<# - .SYNOPSIS - Converts IIS elements to instances of the MSFT_xWebBindingInformation CIM class. -#> -function ConvertTo-CimBinding -{ - [CmdletBinding()] - [OutputType([Microsoft.Management.Infrastructure.CimInstance])] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [AllowEmptyCollection()] - [AllowNull()] - [Object[]] - $InputObject - ) + $inDesiredSate = $true - begin + foreach ($customField in $LogCustomField) { - $cimClassName = 'MSFT_xWebBindingInformation' - $cimNamespace = 'root/microsoft/Windows/DesiredStateConfiguration' - } + $filterString = "/system.applicationHost/sites/site[@name='{0}']/logFile/customFields/add[@logFieldName='{1}']" -f $Site, $customField.LogFieldName + $presentCustomField = Get-WebConfigurationProperty -Filter $filterString -Name "." - process - { - foreach ($binding in $InputObject) + if ($presentCustomField) { - [Hashtable]$cimProperties = @{ - Protocol = [String]$binding.protocol - BindingInformation = [String]$binding.bindingInformation - } - - 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 ([String]::IsNullOrEmpty($IPAddress)) - { - $IPAddress = '*' - } - - $cimProperties.Add('IPAddress', [String]$IPAddress) - $cimProperties.Add('Port', [UInt16]$Port) - $cimProperties.Add('HostName', [String]$HostName) - } - else - { - $cimProperties.Add('IPAddress', [String]::Empty) - $cimProperties.Add('Port', [UInt16]::MinValue) - $cimProperties.Add('HostName', [String]::Empty) - } - - if ([Environment]::OSVersion.Version -ge '6.2') + $sourceNameMatch = $customField.SourceName -eq $presentCustomField.SourceName + $sourceTypeMatch = $customField.SourceType -eq $presentCustomField.sourceType + if (-not ($sourceNameMatch -and $sourceTypeMatch)) { - $cimProperties.Add('SslFlags', [String]$binding.sslFlags) + $inDesiredSate = $false } - - $cimProperties.Add('CertificateThumbprint', [String]$binding.certificateHash) - $cimProperties.Add('CertificateStoreName', [String]$binding.certificateStoreName) - - New-CimInstance -ClassName $cimClassName ` - -Namespace $cimNamespace ` - -Property $cimProperties ` - -ClientOnly } - } -} - -<# - .SYNOPSIS - Converts instances of the MSFT_xWebBindingInformation CIM class to the IIS - element representation. - - .LINK - https://www.iis.net/configreference/system.applicationhost/sites/site/bindings/binding -#> -function ConvertTo-WebBinding -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [AllowEmptyCollection()] - [AllowNull()] - [Object[]] - $InputObject - ) - process - { - foreach ($binding in $InputObject) + else { - $outputObject = @{ - protocol = $binding.Protocol - } - - if ($binding -is [Microsoft.Management.Infrastructure.CimInstance]) - { - if ($binding.Protocol -in @('http', 'https')) - { - if (-not [String]::IsNullOrEmpty($binding.BindingInformation)) - { - if (-not [String]::IsNullOrEmpty($binding.IPAddress) -or - -not [String]::IsNullOrEmpty($binding.Port) -or - -not [String]::IsNullOrEmpty($binding.HostName) - ) - { - $isJoinRequired = $true - Write-Verbose -Message ` - ($LocalizedData.VerboseConvertToWebBindingIgnoreBindingInformation ` - -f $binding.Protocol) - } - else - { - $isJoinRequired = $false - } - } - else - { - $isJoinRequired = $true - } - - # Construct the bindingInformation attribute - if ($isJoinRequired -eq $true) - { - $ipAddressString = Format-IPAddressString -InputString $binding.IPAddress ` - -ErrorAction Stop - - if ([String]::IsNullOrEmpty($binding.Port)) - { - switch ($binding.Protocol) - { - 'http' { $portNumberString = '80' } - 'https' { $portNumberString = '443' } - } - - Write-Verbose -Message ` - ($LocalizedData.VerboseConvertToWebBindingDefaultPort ` - -f $binding.Protocol, $portNumberString) - } - else - { - if (Test-PortNumber -InputString $binding.Port) - { - $portNumberString = $binding.Port - } - else - { - $errorMessage = $LocalizedData.ErrorWebBindingInvalidPort ` - -f $binding.Port - New-TerminatingError -ErrorId 'WebBindingInvalidPort' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidArgument' - } - } - - $bindingInformation = $ipAddressString, ` - $portNumberString, ` - $binding.HostName -join ':' - $outputObject.Add('bindingInformation', [String]$bindingInformation) - } - else - { - $outputObject.Add('bindingInformation', [String]$binding.BindingInformation) - } - } - else - { - if ([String]::IsNullOrEmpty($binding.BindingInformation)) - { - $errorMessage = $LocalizedData.ErrorWebBindingMissingBindingInformation ` - -f $binding.Protocol - New-TerminatingError -ErrorId 'WebBindingMissingBindingInformation' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidArgument' - } - else - { - $outputObject.Add('bindingInformation', [String]$binding.BindingInformation) - } - } - - # SSL-related properties - if ($binding.Protocol -eq 'https') - { - if ([String]::IsNullOrEmpty($binding.CertificateThumbprint)) - { - If ($Binding.CertificateSubject) - { - if ($binding.CertificateSubject.substring(0,3) -ne 'CN=') - { - $binding.CertificateSubject = "CN=$($Binding.CertificateSubject)" - } - $FindCertificateSplat = @{ - Subject = $Binding.CertificateSubject - } - } - else - { - $errorMessage = $LocalizedData.ErrorWebBindingMissingCertificateThumbprint ` - -f $binding.Protocol - New-TerminatingError -ErrorId 'WebBindingMissingCertificateThumbprint' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidArgument' - } - } - - if ([String]::IsNullOrEmpty($binding.CertificateStoreName)) - { - $certificateStoreName = 'MY' - Write-Verbose -Message ` - ($LocalizedData.VerboseConvertToWebBindingDefaultCertificateStoreName ` - -f $certificateStoreName) - } - else - { - $certificateStoreName = $binding.CertificateStoreName - } - - if ($FindCertificateSplat) - { - $FindCertificateSplat.Add('Store',$CertificateStoreName) - $Certificate = Find-Certificate @FindCertificateSplat - if ($Certificate) - { - $certificateHash = $Certificate.Thumbprint - } - else - { - $errorMessage = $LocalizedData.ErrorWebBindingInvalidCertificateSubject ` - -f $binding.CertificateSubject, $binding.CertificateStoreName - New-TerminatingError -ErrorId 'WebBindingInvalidCertificateSubject' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidArgument' - } - } - - # Remove the Left-to-Right Mark character - if ($certificateHash) - { - $certificateHash = $certificateHash -replace '^\u200E' - } - else - { - $certificateHash = $binding.CertificateThumbprint -replace '^\u200E' - } - - $outputObject.Add('certificateHash', [String]$certificateHash) - $outputObject.Add('certificateStoreName', [String]$certificateStoreName) - - if ([Environment]::OSVersion.Version -ge '6.2') - { - $sslFlags = [Int64]$binding.SslFlags - - if ($sslFlags -in @(1, 3) -and [String]::IsNullOrEmpty($binding.HostName)) - { - $errorMessage = $LocalizedData.ErrorWebBindingMissingSniHostName - New-TerminatingError -ErrorId 'WebBindingMissingSniHostName' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidArgument' - } - - $outputObject.Add('sslFlags', $sslFlags) - } - } - else - { - # Ignore SSL-related properties for non-SSL bindings - $outputObject.Add('certificateHash', [String]::Empty) - $outputObject.Add('certificateStoreName', [String]::Empty) - - if ([Environment]::OSVersion.Version -ge '6.2') - { - $outputObject.Add('sslFlags', [Int64]0) - } - } - } - else - { - <# - WebAdministration can throw the following exception if there are non-standard - bindings (such as 'net.tcp'): 'The data is invalid. - (Exception from HRESULT: 0x8007000D)' - - Steps to reproduce: - 1) Add 'net.tcp' binding - 2) Execute {Get-Website | ` - ForEach-Object {$_.bindings.Collection} | ` - Select-Object *} - - Workaround is to create a new custom object and use dot notation to - access binding properties. - #> - - $outputObject.Add('bindingInformation', [String]$binding.bindingInformation) - $outputObject.Add('certificateHash', [String]$binding.certificateHash) - $outputObject.Add('certificateStoreName', [String]$binding.certificateStoreName) - - if ([Environment]::OSVersion.Version -ge '6.2') - { - $outputObject.Add('sslFlags', [Int64]$binding.sslFlags) - } - } - - Write-Output -InputObject ([PSCustomObject]$outputObject) - } - } -} - -<# - .SYNOPSIS - Converts IIS custom log field collection to instances of the MSFT_xLogCustomFieldInformation CIM class. -#> -function ConvertTo-CimLogCustomFields -{ - [CmdletBinding()] - [OutputType([Microsoft.Management.Infrastructure.CimInstance[]])] - param - ( - [Parameter(Mandatory = $true)] - [AllowEmptyCollection()] - [AllowNull()] - [Object[]] - $InputObject - ) - - $cimClassName = 'MSFT_xLogCustomFieldInformation' - $cimNamespace = 'root/microsoft/Windows/DesiredStateConfiguration' - $cimCollection = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]' - - foreach ($customField in $InputObject) - { - $cimProperties = @{ - LogFieldName = $customField.LogFieldName - SourceName = $customField.SourceName - SourceType = $customField.SourceType + $inDesiredSate = $false } - - $cimCollection += (New-CimInstance -ClassName $cimClassName ` - -Namespace $cimNamespace ` - -Property $cimProperties ` - -ClientOnly) } - return $cimCollection + return $inDesiredSate } - -<# - .SYNOPSYS - Formats the input IP address string for use in the bindingInformation attribute. -#> -function Format-IPAddressString -{ - [CmdletBinding()] - [OutputType([String])] - param - ( - [Parameter(Mandatory = $true)] - [AllowEmptyString()] - [AllowNull()] - [String] - $InputString - ) - - if ([String]::IsNullOrEmpty($InputString) -or $InputString -eq '*') - { - $outputString = '*' - } - else - { - try - { - $ipAddress = [IPAddress]::Parse($InputString) - - switch ($ipAddress.AddressFamily) - { - 'InterNetwork' - { - $outputString = $ipAddress.IPAddressToString - } - 'InterNetworkV6' - { - $outputString = '[{0}]' -f $ipAddress.IPAddressToString - } - } - } - catch - { - $errorMessage = $LocalizedData.ErrorWebBindingInvalidIPAddress ` - -f $InputString, $_.Exception.Message - New-TerminatingError -ErrorId 'WebBindingInvalidIPAddress' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidArgument' - } - } - - return $outputString -} - -<# - .SYNOPSIS - Helper function used to validate that the authenticationProperties for an Application. - - .PARAMETER Site - Specifies the name of the Website. -#> -function Get-AuthenticationInfo -{ - [CmdletBinding()] - [OutputType([Microsoft.Management.Infrastructure.CimInstance])] - param - ( - [Parameter(Mandatory = $true)] - [String]$Site - ) - - $authenticationProperties = @{} - foreach ($type in @('Anonymous', 'Basic', 'Digest', 'Windows')) - { - $authenticationProperties[$type] = [Boolean](Test-AuthenticationEnabled -Site $Site ` - -Type $type) - } - - return New-CimInstance ` - -ClassName MSFT_xWebAuthenticationInformation ` - -ClientOnly -Property $authenticationProperties ` - -NameSpace 'root\microsoft\windows\desiredstateconfiguration' -} - -<# - .SYNOPSIS - Helper function used to build a default CimInstance for AuthenticationInformation -#> -function Get-DefaultAuthenticationInfo -{ - New-CimInstance -ClassName MSFT_xWebAuthenticationInformation ` - -ClientOnly ` - -Property @{ Anonymous = $false; Basic = $false; Digest = $false; Windows = $false } ` - -NameSpace 'root\microsoft\windows\desiredstateconfiguration' -} - -<# - .SYNOPSIS - Helper function used to set authenticationProperties for an Application - - .PARAMETER Site - Specifies the name of the Website. - - .PARAMETER Type - Specifies the type of Authentication. - Limited to the set: ('Anonymous','Basic','Digest','Windows') - - .PARAMETER Enabled - Whether the Authentication is enabled or not. -#> -function Set-Authentication -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [String]$Site, - - [Parameter(Mandatory = $true)] - [ValidateSet('Anonymous','Basic','Digest','Windows')] - [String]$Type, - - [Boolean]$Enabled - ) - - Set-WebConfigurationProperty ` - -Filter /system.WebServer/security/authentication/${Type}Authentication ` - -Name enabled ` - -Value $Enabled ` - -Location $Site -} - -<# - .SYNOPSIS - Helper function used to validate that the authenticationProperties for an Application. - - .PARAMETER Site - Specifies the name of the Website. - - .PARAMETER AuthenticationInfo - A CimInstance of what state the AuthenticationInfo should be. -#> -function Set-AuthenticationInfo -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [String]$Site, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [Microsoft.Management.Infrastructure.CimInstance]$AuthenticationInfo - ) - - foreach ($type in @('Anonymous', 'Basic', 'Digest', 'Windows')) - { - $enabled = ($AuthenticationInfo.CimInstanceProperties[$type].Value -eq $true) - Set-Authentication -Site $Site -Type $type -Enabled $enabled - } -} - -<# - .SYNOPSIS - Helper function used to set the LogCustomField for a website. - - .PARAMETER Site - Specifies the name of the Website. - - .PARAMETER LogCustomField - A CimInstance collection of what the LogCustomField should be. -#> -function Set-LogCustomField -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [String] - $Site, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [Microsoft.Management.Infrastructure.CimInstance[]] - $LogCustomField - ) - - $setCustomFields = @() - foreach ($customField in $LogCustomField) - { - $setCustomFields += @{ - logFieldName = $customField.LogFieldName - sourceName = $customField.SourceName - sourceType = $customField.SourceType - } - } - - # The second Set-WebConfigurationProperty is to handle an edge case where logfile.customFields is not updated correctly. May be caused by a possible bug in the IIS provider - for ($i = 1; $i -le 2; $i++) - { - Set-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' -Filter "system.applicationHost/sites/site[@name='$Site']/logFile/customFields" -Name "." -Value $setCustomFields - } -} - -<# - .SYNOPSIS - Helper function used to test the authenticationProperties state for an Application. - Will return that value which will either [String]True or [String]False - - .PARAMETER Site - Specifies the name of the Website. - - .PARAMETER Type - Specifies the type of Authentication. - Limited to the set: ('Anonymous','Basic','Digest','Windows'). -#> -function Test-AuthenticationEnabled -{ - [CmdletBinding()] - [OutputType([Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [String]$Site, - - [Parameter(Mandatory = $true)] - [ValidateSet('Anonymous','Basic','Digest','Windows')] - [String]$Type - ) - - - $prop = Get-WebConfigurationProperty ` - -Filter /system.WebServer/security/authentication/${Type}Authentication ` - -Name enabled ` - -Location $Site - - return $prop.Value -} - -<# - .SYNOPSIS - Helper function used to test the authenticationProperties state for an Application. - Will return that result for use in Test-TargetResource. Uses Test-AuthenticationEnabled - to determine this. First incorrect result will break this function out. - - .PARAMETER Site - Specifies the name of the Website. - - .PARAMETER AuthenticationInfo - A CimInstance of what state the AuthenticationInfo should be. -#> -function Test-AuthenticationInfo -{ - [CmdletBinding()] - [OutputType([Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [String]$Site, - - [Parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] - [Microsoft.Management.Infrastructure.CimInstance]$AuthenticationInfo - ) - - $result = $true - - foreach ($type in @('Anonymous', 'Basic', 'Digest', 'Windows')) - { - $expected = $AuthenticationInfo.CimInstanceProperties[$type].Value - $actual = Test-AuthenticationEnabled -Site $Site -Type $type - if ($expected -ne $actual) - { - $result = $false - break - } - } - - return $result -} - -<# - .SYNOPSIS - Validates the desired binding information (i.e. no duplicate IP address, port, and - host name combinations). -#> -function Test-BindingInfo -{ - [CmdletBinding()] - [OutputType([Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [Microsoft.Management.Infrastructure.CimInstance[]] - $BindingInfo - ) - - $isValid = $true - - try - { - # Normalize the input (helper functions will perform additional validations) - $bindings = @(ConvertTo-WebBinding -InputObject $bindingInfo | ConvertTo-CimBinding) - $standardBindings = @($bindings | ` - Where-Object -FilterScript {$_.Protocol -in @('http', 'https')}) - $nonStandardBindings = @($bindings | ` - Where-Object -FilterScript {$_.Protocol -notin @('http', 'https')}) - - if ($standardBindings.Count -ne 0) - { - # IP address, port, and host name combination must be unique - if (($standardBindings | Group-Object -Property IPAddress, Port, HostName) | ` - Where-Object -FilterScript {$_.Count -ne 1}) - { - $isValid = $false - Write-Verbose -Message ` - ($LocalizedData.VerboseTestBindingInfoSameIPAddressPortHostName) - } - - # A single port cannot be simultaneously specified for bindings with different protocols - foreach ($groupByPort in ($standardBindings | Group-Object -Property Port)) - { - if (($groupByPort.Group | Group-Object -Property Protocol).Length -ne 1) - { - $isValid = $false - Write-Verbose -Message ` - ($LocalizedData.VerboseTestBindingInfoSamePortDifferentProtocol) - break - } - } - } - - if ($nonStandardBindings.Count -ne 0) - { - if (($nonStandardBindings | ` - Group-Object -Property Protocol, BindingInformation) | ` - Where-Object -FilterScript {$_.Count -ne 1}) - { - $isValid = $false - Write-Verbose -Message ` - ($LocalizedData.VerboseTestBindingInfoSameProtocolBindingInformation) - } - } - } - catch - { - $isValid = $false - Write-Verbose -Message ($LocalizedData.VerboseTestBindingInfoInvalidCatch ` - -f $_.Exception.Message) - } - - return $isValid -} - -<# - .SYNOPSIS - Validates that an input string represents a valid port number. - The port number must be a positive integer between 1 and 65535. -#> -function Test-PortNumber -{ - [CmdletBinding()] - [OutputType([Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [AllowEmptyString()] - [AllowNull()] - [String] - $InputString - ) - - try - { - $isValid = [UInt16]$InputString -ne 0 - } - catch - { - $isValid = $false - } - - return $isValid -} - -<# - .SYNOPSIS - Helper function used to validate and compare website bindings of current to desired. - Returns True if bindings do not need to be updated. -#> -function Test-WebsiteBinding -{ - [CmdletBinding()] - [OutputType([Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $Name, - - [Parameter(Mandatory = $true)] - [Microsoft.Management.Infrastructure.CimInstance[]] - $BindingInfo - ) - - $inDesiredState = $true - - # Ensure that desired binding information is valid (i.e. no duplicate IP address, port, and - # host name combinations). - if (-not (Test-BindingInfo -BindingInfo $BindingInfo)) - { - $errorMessage = $LocalizedData.ErrorWebsiteBindingInputInvalidation ` - -f $Name - New-TerminatingError -ErrorId 'WebsiteBindingInputInvalidation' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidResult' - } - - try - { - $website = Get-Website | Where-Object -FilterScript {$_.Name -eq $Name} - - # Normalize binding objects to ensure they have the same representation - $currentBindings = @(ConvertTo-WebBinding -InputObject $website.bindings.Collection ` - -Verbose:$false) - $desiredBindings = @(ConvertTo-WebBinding -InputObject $BindingInfo ` - -Verbose:$false) - - $propertiesToCompare = 'protocol', ` - 'bindingInformation', ` - 'certificateHash', ` - 'certificateStoreName' - - # The sslFlags attribute was added in IIS 8.0. - # This check is needed for backwards compatibility with Windows Server 2008 R2. - if ([Environment]::OSVersion.Version -ge '6.2') - { - $propertiesToCompare += 'sslFlags' - } - - if (Compare-Object -ReferenceObject $currentBindings ` - -DifferenceObject $desiredBindings ` - -Property $propertiesToCompare) - { - $inDesiredState = $false - } - } - catch - { - $errorMessage = $LocalizedData.ErrorWebsiteCompareFailure ` - -f $Name, $_.Exception.Message - New-TerminatingError -ErrorId 'WebsiteCompareFailure' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidResult' - } - - return $inDesiredState -} - -<# - .SYNOPSIS - Helper function used to test the LogCustomField state for a website. - - .PARAMETER Site - Specifies the name of the Website. - - .PARAMETER LogCustomField - A CimInstance collection of what state the LogCustomField should be. -#> -function Test-LogCustomField -{ - [CmdletBinding()] - [OutputType([Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [String] - $Site, - - [Parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] - [Microsoft.Management.Infrastructure.CimInstance[]] - $LogCustomField - ) - - $inDesiredSate = $true - - foreach ($customField in $LogCustomField) - { - $filterString = "/system.applicationHost/sites/site[@name='{0}']/logFile/customFields/add[@logFieldName='{1}']" -f $Site, $customField.LogFieldName - $presentCustomField = Get-WebConfigurationProperty -Filter $filterString -Name "." - - if ($presentCustomField) - { - $sourceNameMatch = $customField.SourceName -eq $presentCustomField.SourceName - $sourceTypeMatch = $customField.SourceType -eq $presentCustomField.sourceType - if (-not ($sourceNameMatch -and $sourceTypeMatch)) - { - $inDesiredSate = $false - } - } - else - { - $inDesiredSate = $false - } - } - - return $inDesiredSate -} - -<# - .SYNOPSIS - Helper function used to update default pages of website. -#> -function Update-DefaultPage -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [String] - $Name, - - [Parameter(Mandatory = $true)] - [String[]] - $DefaultPage - ) - - $allDefaultPages = @( - Get-WebConfiguration -Filter '/system.webServer/defaultDocument/files/*' ` - -PSPath "IIS:\Sites\$Name" | - ForEach-Object -Process { Write-Output -InputObject $_.value } - ) - - foreach ($page in $DefaultPage) - { - if ($allDefaultPages -inotcontains $page) - { - Add-WebConfiguration -Filter '/system.webServer/defaultDocument/files' ` - -PSPath "IIS:\Sites\$Name" ` - -Value @{ value = $page } - Write-Verbose -Message ($LocalizedData.VerboseUpdateDefaultPageUpdated ` - -f $Name, $page) - } - } -} - -<# - .SYNOPSIS - Updates website bindings. -#> -function Update-WebsiteBinding -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String] - $Name, - - [Parameter(Mandatory = $false)] - [Microsoft.Management.Infrastructure.CimInstance[]] - $BindingInfo - ) - - # Use Get-WebConfiguration instead of Get-Website to retrieve XPath of the target website. - # XPath -Filter is case-sensitive. Use Where-Object to get the target website by name. - $website = Get-WebConfiguration -Filter '/system.applicationHost/sites/site' | - Where-Object -FilterScript {$_.Name -eq $Name} - - if (-not $website) - { - $errorMessage = $LocalizedData.ErrorWebsiteNotFound ` - -f $Name - New-TerminatingError -ErrorId 'WebsiteNotFound' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidResult' - } - - ConvertTo-WebBinding -InputObject $BindingInfo -ErrorAction Stop | - ForEach-Object -Begin { - Clear-WebConfiguration -Filter "$($website.ItemXPath)/bindings" -Force -ErrorAction Stop - } -Process { - - $properties = $_ - - try - { - Add-WebConfiguration -Filter "$($website.ItemXPath)/bindings" -Value @{ - protocol = $properties.protocol - bindingInformation = $properties.bindingInformation - } -Force -ErrorAction Stop - } - catch - { - $errorMessage = $LocalizedData.ErrorWebsiteBindingUpdateFailure ` - -f $Name, $_.Exception.Message - New-TerminatingError -ErrorId 'WebsiteBindingUpdateFailure' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidResult' - } - - if ($properties.protocol -eq 'https') - { - if ([Environment]::OSVersion.Version -ge '6.2') - { - try - { - Set-WebConfigurationProperty ` - -Filter "$($website.ItemXPath)/bindings/binding[last()]" ` - -Name sslFlags ` - -Value $properties.sslFlags ` - -Force ` - -ErrorAction Stop - } - catch - { - $errorMessage = $LocalizedData.ErrorWebsiteBindingUpdateFailure ` - -f $Name, $_.Exception.Message - New-TerminatingError ` - -ErrorId 'WebsiteBindingUpdateFailure' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidResult' - } - } - - try - { - $binding = Get-WebConfiguration ` - -Filter "$($website.ItemXPath)/bindings/binding[last()]" ` - -ErrorAction Stop - $binding.AddSslCertificate($properties.certificateHash, ` - $properties.certificateStoreName) - } - catch - { - $errorMessage = $LocalizedData.ErrorWebBindingCertificate ` - -f $properties.certificateHash, $_.Exception.Message - New-TerminatingError ` - -ErrorId 'WebBindingCertificate' ` - -ErrorMessage $errorMessage ` - -ErrorCategory 'InvalidOperation' - } - } - } -} - #endregion Export-ModuleMember -Function *-TargetResource diff --git a/Examples/Sample_xFTP_NewFTPSite.ps1 b/Examples/Sample_xFTP_NewFTPSite.ps1 new file mode 100644 index 000000000..2580fe047 --- /dev/null +++ b/Examples/Sample_xFTP_NewFTPSite.ps1 @@ -0,0 +1,69 @@ +configuration Sample_FTP_NewFTPsite +{ + param( + + # Target nodes to apply the configuration + [string[]] $NodeName = 'localhost', + + # Name of the website to create + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [String] $Name, + + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [String] $FTPSitePath, + + [Parameter(Mandatory)] + [String] $CertificateThumbprint, + + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [String] $FTPLogPath + + ) + + Import-DscResource -ModuleName xWebAdministration + + Node $NodeName + { + FTP NewFTPSite + { + Ensure = 'Present' + Name = $Name + ApplicationPool = 'DefaultAppPool' + PhysicalPath = $FTPSitePath + State = 'Started' + AuthorizationInfo = @( + MSFT_FTPAuthorizationInformation + { + AccessType = 'Allow' + Users = 'User1' + Roles = '' + Permissions = 'Read' + }) + BindingInfo = ` + MSFT_FTPBindingInformation + { + Protocol = 'ftp' + Port = '21' + HostName = 'ftp.somesite.com' + } + SslInfo = ` + MSFT_FTPSslInformation + { + ControlChannelPolicy = 'SslAllow' + DataChannelPolicy = 'SslAllow' + RequireSsl128 = $true + CertificateThumbprint = $CertificateThumbprint + CertificateStoreName = 'My' + } + LogPath = $FTPLogPath + LogFlags = @('Date','Time','ClientIP','UserName','ServerIP','Method','UriStem') + LogPeriod = 'Hourly' + LoglocalTimeRollover = $true + DirectoryBrowseFlags = 'StyleUnix' + UserIsolation = 'IsolateAllDirectories' + } + } +} diff --git a/README.md b/README.md index f626102c4..8ace734ed 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # xWebAdministration -The **xWebAdministration** module contains the **xIISModule**, **xIISLogging**, **xWebAppPool**, **xWebsite**, **xWebApplication**, **xWebVirtualDirectory**, **xSSLSettings**, **xWebConfigKeyValue**, **xWebConfigProperty**, **xWebConfigPropertyCollection** and **WebApplicationHandler** DSC resources for creating and configuring various IIS artifacts. +The **xWebAdministration** module contains the **FTP**, **xIISModule**, **xIISLogging**, **xWebAppPool**, **xWebsite**, **xWebApplication**, **xWebVirtualDirectory**, **xSSLSettings**, **xWebConfigKeyValue**, **xWebConfigProperty**, **xWebConfigPropertyCollection** and **WebApplicationHandler** 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. @@ -31,6 +31,53 @@ Please check out common DSC Resources [contributing guidelines](https://github.c ## Resources +### FTP + +* **Ensure**: Ensures that the FTP Site is **Present** or **Absent**. +* **Name**: The desired name of the website. +* **PhysicalPath**: The path to the files that compose the website. +* **PhysicalPathAccessAccount**: Specific username used for access to physical path. *Note* In case of using SMB as a physical path and target server doesn't share identity database with device/server hosting the share, local user account must be created with the same username/password used for the access, section 'More Information' [support.microsoft.com](https://support.microsoft.com/en-us/help/247099/access-denied-when-connecting-to-a-ftp-directory-that-uses-a-unc-path) +* **PhysicalPathAccessPass**: Specifies password used for access to physical path. +* **State**: The state of the website: { Started | Stopped } +* **ApplicationPool**: The FTP Site’s application pool. +* **AuthenticationInformation**: FTP Site's authentication information in the form of an embedded instance of the **MSFT_FTPAuthenticationInformation** CIM class. **MSFT_FTPAuthenticationInformation** take the following properties: + * **Anonymous**: The acceptable values for this property are: `$true`, `$false` + * **Basic**: The acceptable values for this property are: `$true`, `$false` +* **AuthorizationInformation**: FTP Site's authorization information in the form of an array of embedded instances of the **MSFT_FTPAuthorizationInformation** CIM class. **MSFT_FTPAuthorizationInformation** take the following properties: + * **AccessType**: The acceptable values for this property are: `Allow`, `Deny` + * **Users**: Users which can have desired access. *Note* If using groups pass in '' for the users. To add authorization information for 'All Users' specify `'*'` as a value and for 'All Anonymous Users' - `'?'`. + * **Roles**: Groups which can have desired access. *Note* If using users pass in '' for the group. + * **Permissions**: The acceptable values for this property are: `Read`, `Write`, `Read,Write` +* **BindingInfo**: Website's binding information in the form of an array of embedded instances of the **MSFT_FTPBindingInformation** CIM class that implements the following properties: + * **Protocol**: The protocol of the binding. This property is required. The acceptable values for this property are: `ftp`. + * **BindingInformation**: The binding information in the form a colon-delimited string that includes the IP address, port, and host name of the binding. This property is ignored for `http` and `https` bindings if at least one of the following properties is specified: **IPAddress**, **Port**, **HostName**. + * **IPAddress**: The IP address of the binding. This property is only applicable for `http` and `https` bindings. The default value is `*`. + * **Port**: The port of the binding. The value must be a positive integer between `1` and `65535`. This property is only applicable for `http` (the default value is `80`) and `https` (the default value is `443`) bindings. + * **HostName**: The host name of the binding. This property is only applicable for `http` and `https` bindings. +* **SslInfo**: FTP Site's ssl information in the form of an embedded instance of the **MSFT_FTPSslInformation** CIM class. **MSFT_FTPSslInformation** takes the following properties: + * **ControlChannelPolicy**: The acceptable values for this property are: `SslAllow`, `SslRequire`, `SslRequireCredentialsOnly`},Values{`SslAllow`, `SslRequire`, `SslRequireCredentialsOnly` + * **DataChannelPolicy**: The acceptable values for this property are: `SslAllow`, `SslRequire`, `SslDeny` + * **RequireSsl128**: `$true`, `$false` + * **CertificateThumbprint**:The thumbprint of the certificate. + * **CertificateStoreName**: The name of the certificate store where the certificate is located. The acceptable values for this property are: `My`, `WebHosting`. The default value is `My`. +* **FirewallIPAddress**: The external firewall IP address behind which FTP server is located, used for passive connections. +* **StartingDataChannelPort**: The starting data channel port number for passive connections. *Note* The valid value is either 0 or from 1025 to 65535. Special port range of `0` for StartingDataChannelPort and for `0` EndingDataChannelPort means range of 1025-5000. Ports from 1 through 1024 are reserved for use by system services. +* **EndingDataChannelPort**: The ending data channel port number for passive connections. *Note* The valid value is either 0 or from 1025 to 65535. Special port range of `0` for StartingDataChannelPort and for `0` EndingDataChannelPort means range of 1025-5000. Ports from 1 through 1024 are reserved for use by system services. +* **GreetingMessage**: Specifies the message the FTP server displays when FTP clients have logged in to the FTP server. +* **ExitMessage**: Specifies the message the FTP server displays when FTP clients log off the FTP server. +* **BannerMessage**: Specifies the message the FTP server displays when FTP clients first connect to the FTP server. +* **MaxClientsMessage**: Specifies the message the FTP server displays when clients try to connect and cannot because the FTP service has reached the maximum number of client connections allowed. +* **SuppressDefaultBanner**: Specifies whether to display the default identification banner for the FTP server. If enabled, displays the default banner; otherwise, the default banner is not displayed. Valid values are: `$true`, `$false` +* **AllowLocalDetailedErrors**: Specifies whether to display detailed error messages when the FTP client is connecting to the FTP server on the server itself. If enabled, displays detailed error messages only to the local host; otherwise, detailed error messages are not displayed. Valid values are: `$true`, `$false` +* **ExpandVariablesInMessages**: Specifies whether to display a specific set of user variables in FTP messages. If enabled, displays user variables in FTP messages; otherwise, all message text will be displayed as entered. Valid values are: `$true`, `$false`. The supported user variables can be found using next link [go.microsoft.com](https://go.microsoft.com/fwlink/?LinkId=210500). +* **LogPath**: The directory to be used for logfiles. +* **LogFlags**: The W3C logging fields: The values that are allowed for this property are: `Date`,`Time`,`ClientIP`,`UserName`,`ServerIP`,`Method`,`UriStem`,`UriQuery`,`HttpStatus`,`Win32Status`,`TimeTaken`,`ServerPort`,`UserAgent`,`Referer`,`HttpSubStatus` +* **LogPeriod**: How often the log file should rollover. The values that are allowed for this property are: `Hourly`,`Daily`,`Weekly`,`Monthly`,`MaxSize`. *Note* If LogTruncateSize property is set the LogPeriod property will be ignored. +* **LogTruncateSize**: How large the file should be before it is truncated. If this is set then LogPeriod will be ignored if passed in and set to MaxSize. The value must be a valid integer between `1048576 (1MB)` and `4294967295 (4GB)`. +* **LoglocalTimeRollover**: Use the localtime for file naming and rollover. The acceptable values for this property are: `$true`, `$false` +* **DirectoryBrowseFlags**: What method of Directory Browsing should be enabled. The values that are allowed for this property are: `StyleUnix`,`LongDate`,`DisplayAvailableBytes`,`DisplayVirtualDirectories` +* **UserIsolation**: What method of UserIsolation should be enabled. The values that are allowed for this property are: `None`,`StartInUsersDirectory`,`IsolateAllDirectories`,`IsolateRootDirectoryOnly` + ### xIisHandler (DEPRECATED) > Please use WebApplicationHandler resource instead. xIISHandler will be removed in future release @@ -320,6 +367,9 @@ This resource manages the IIS configuration section locking (overrideMode) to co ### Unreleased +* Added **FTP** resource for managing FTP sites [#81](https://github.com/PowerShell/xWebAdministration/issues/81) +* BEHAVIOR CHANGED: For **xWebsite** and **xWebApplcation** if AuthenticationInformation was not specified Default($false) is assumed. + ### 2.6.0.0 * Changed order of classes in schema.mof files to workaround [#423](https://github.com/PowerShell/xWebAdministration/issues/423) * Fix subject comparison multiple entries for helper function `Find-Certificate` that could not find the test diff --git a/Tests/Integration/MSFT_FTP.Integration.Tests.ps1 b/Tests/Integration/MSFT_FTP.Integration.Tests.ps1 new file mode 100644 index 000000000..71b4256f8 --- /dev/null +++ b/Tests/Integration/MSFT_FTP.Integration.Tests.ps1 @@ -0,0 +1,229 @@ +$script:DSCModuleName = 'xWebAdministration' +$script:DSCResourceName = 'MSFT_FTP' + +#region HEADER +[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) +if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +$TestEnvironment = Initialize-TestEnvironment -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Integration +#endregion + +[string] $tempName = "$($script:DSCResourceName)_" + (Get-Date).ToString('yyyyMMdd_HHmmss') + +try +{ + # Now that xWebAdministration should be discoverable load the configuration data + $ConfigFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:DSCResourceName).config.ps1" + . $ConfigFile + + $null = Backup-WebConfiguration -Name $tempName + + $DSCConfig = Import-LocalizedData -BaseDirectory $PSScriptRoot -FileName "$($script:DSCResourceName).config.psd1" + + # Create a SelfSigned Cert + $SelfSignedCert = (New-SelfSignedCertificate -DnsName $DSCConfig.AllNodes.BindingInfoHostName -CertStoreLocation 'cert:\LocalMachine\My') + + # Create a folder if it's absent + if (-not [bool]([System.Uri]$DSCConfig.AllNodes.PhysicalPath).IsUnc -and ` + -not(Test-Path -Path $DSCConfig.AllNodes.PhysicalPath)) + { + New-Item -Path $DSCConfig.AllNodes.PhysicalPath -ItemType Directory -Force | Out-Null + } + + # Create a test user if it's absent + $mockUser = Get-LocalUser -Name $DSCConfig.AllNodes.PhysicalPathAccessAccount -ErrorAction SilentlyContinue + if (-not $mockUser) + { + $mockUser = New-LocalUser ` + -Name $DSCConfig.AllNodes.PhysicalPathAccessAccount ` + -Password (ConvertTo-SecureString -String $DSCConfig.AllNodes.PhysicalPathAccessPass -AsPlainText -Force) ` + -AccountNeverExpires:$true ` + -UserMayNotChangePassword:$true + } + + #region HelperFunctions + + # Function needed to test AuthenticationInfo + Function Get-AuthenticationInfo ($Type, $Website) + { + (Get-ItemProperty "IIS:\Sites\$Website" -Name ftpServer.security.authentication."${Type}Authentication".enabled).Value + } + + function Get-AuthorizationInfo ($Website) + { + (Get-WebConfiguration -Filter '/system.ftpServer/security/authorization' -Location $Website).Collection + } + + function Get-SslInfo ($Type, $Website) + { + $correctType = switch($Type) + { + CertificateThumbprint { 'serverCertHash' } + CertificateStoreName { 'serverCertStoreName' } + RequireSsl128 { 'ssl128' } + ControlChannelPolicy { 'controlChannelPolicy' } + DataChannelPolicy { 'dataChannelPolicy' } + } + (Get-Item -Path IIS:\Sites\${Website}\).ftpServer.security.ssl.${correctType} + } + + function Get-DataChannelPorts + { + Get-WebConfiguration -Filter '/system.ftpServer/firewallSupport' + } + #endregion + + Describe "$($script:DSCResourceName)_Present" { + #region DEFAULT TESTS + It 'Should compile without throwing' { + { + Invoke-Expression -Command "$($script:DSCResourceName)_Present -ConfigurationData `$DSCConfig -OutputPath `$TestDrive -CertificateThumbprint `$SelfSignedCert.Thumbprint" + 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 + } + + It 'should return True when calling Test-DscConfiguration' { + $result = Test-DscConfiguration + + $result | Should -Be $true + } + #endregion + + It 'Should Create a Started FTP site with correct settings' -test { + Invoke-Expression -Command "$($script:DSCResourceName)_Present -ConfigurationData `$DSCConfg -OutputPath `$TestDrive -CertificateThumbprint `$SelfSignedCert.Thumbprint" + + # Build results to test + $result = Get-Website -Name $DSCConfig.AllNodes.Name + + # Test basic settings are correct + $result.Name | Should -Be $DSCConfig.AllNodes.Name + $result.PhysicalPath | Should -Be $DSCConfig.AllNodes.PhysicalPath + $result.userName | Should -be $DSCConfig.AllNodes.PhysicalPathAccessAccount + $result.password | Should -be $DSCConfig.AllNodes.PhysicalPathAccessPass + $result.State | Should -Be 'Started' + $result.ApplicationPool | Should -Be $DSCConfig.AllNodes.ApplicationPool + + # Test that AuthenticationInfo is correct + Get-AuthenticationInfo -Type 'Anonymous' -Website $DSCConfig.AllNodes.Name | Should -Be $DSCConfig.AllNodes.AuthenticationInfoAnonymous + Get-AuthenticationInfo -Type 'Basic' -Website $DSCConfig.AllNodes.Name | Should -Be $DSCConfig.AllNodes.AuthenticationInfoBasic + + # Test bindings are correct + $result.bindings.Collection.Protocol | Should -Be $DSCConfig.AllNodes.BindingInfoProtocol + $result.bindings.Collection.BindingInformation | Should Match $DSCConfig.AllNodes.BindingInfoPort + $result.bindings.Collection.BindingInformation | Should Match $DSCConfig.AllNodes.BindingInfoHostName + + # Test that AuthorizationInfo is correct + $Authorization = Get-AuthorizationInfo -Website $DSCConfig.AllNodes.Name + + $Authorization[0].accessType | Should -Be $DSCConfig.AllNodes.AuthorizationInfoAccessType1 + $Authorization[0].users | Should -Be $DSCConfig.AllNodes.AuthorizationInfoUsers1 + $Authorization[0].roles | Should BeNullOrEmpty + $Authorization[0].permissions | Should -Be $DSCConfig.AllNodes.AuthorizationInfoPermissions1 + + $Authorization[1].accessType | Should -Be $DSCConfig.AllNodes.AuthorizationInfoAccessType1 + $Authorization[1].users | Should -Be $DSCConfig.AllNodes.AuthorizationInfoUsers2 + $Authorization[1].roles | Should BeNullOrEmpty + $Authorization[1].permissions | Should -Be $DSCConfig.AllNodes.AuthorizationInfoPermissions3 + + $Authorization[2].accessType | Should -Be $DSCConfig.AllNodes.AuthorizationInfoAccessType2 + $Authorization[2].users | Should -Be $DSCConfig.AllNodes.AuthorizationInfoUsers3 + $Authorization[2].roles | Should BeNullOrEmpty + $Authorization[2].permissions | Should -Be $DSCConfig.AllNodes.AuthorizationInfoPermissions1 + + $Authorization[3].accessType | Should -Be $DSCConfig.AllNodes.AuthorizationInfoAccessType1 + $Authorization[3].users | Should BeNullOrEmpty + $Authorization[3].roles | Should -Be $DSCConfig.AllNodes.AuthorizationInfoRoles + $Authorization[3].permissions | Should -Be $DSCConfig.AllNodes.AuthorizationInfoPermissions1 + + $Authorization[4].accessType | Should -Be $DSCConfig.AllNodes.AuthorizationInfoAccessType2 + $Authorization[4].users | Should BeNullOrEmpty + $Authorization[4].roles | Should -Be $DSCConfig.AllNodes.AuthorizationInfoRoles + $Authorization[4].permissions | Should -Be $DSCConfig.AllNodes.AuthorizationInfoPermissions2 + + # Test SslInfo + Get-SslInfo -Type ControlChannelPolicy -Website $DSCConfig.AllNodes.Name | Should -Be $DSCConfig.AllNodes.SslInfoControlChannelPolicy + Get-SslInfo -Type DataChannelPolicy -Website $DSCConfig.AllNodes.Name | Should -Be $DSCConfig.AllNodes.SslInfoDataChannelPolicy + Get-SslInfo -Type RequireSsl128 -Website $DSCConfig.AllNodes.Name | Should -Be $DSCConfig.AllNodes.SslInfoRequireSsl128 + Get-SslInfo -Type CertificateThumbprint -Website $DSCConfig.AllNodes.Name | Should -Be $SelfSignedCert.Thumbprint + Get-SslInfo -Type CertificateStoreName -Website $DSCConfig.AllNodes.Name | Should -Be $DSCConfig.AllNodes.SslInfoCertificateStoreName + + # Test firewall support settings + $result.ftpServer.firewallSupport.externalIp4Address | Should -Be $DSCConfig.AllNodes.FirewallIPaddress + (Get-DataChannelPorts).lowDataChannelPort | Should -Be $DSCConfig.AllNodes.StartingDataChannelPort + (Get-DataChannelPorts).highDataChannelPort | Should -Be $DSCConfig.AllNodes.EndingDataChannelPort + + # Test messages section + $result.ftpServer.messages.greetingMessage | Should -Be $DSCConfig.AllNodes.GreetingMessage + $result.ftpServer.messages.exitMessage | Should -Be $DSCConfig.AllNodes.ExitMessage + $result.ftpServer.messages.bannerMessage | Should -Be $DSCConfig.AllNodes.BannerMessage + $result.ftpServer.messages.maxClientsMessage | Should -Be $DSCConfig.AllNodes.MaxClientsMessage + $result.ftpServer.messages.suppressDefaultBanner | Should -Be $DSCConfig.AllNodes.SuppressDefaultBanner + $result.ftpServer.messages.allowLocalDetailedErrors | Should -Be $DSCConfig.AllNodes.AllowLocalDetailedErrors + $result.ftpServer.messages.expandVariables | Should -Be $DSCConfig.AllNodes.ExpandVariablesInMessages + + # Test Log Settings + $result.ftpServer.logFile.logExtFileFlags.Split(',') | Should -BeIn $DSCConfig.AllNodes.LogFlags + $result.ftpServer.logFile.directory | Should -Be $DSCConfig.AllNodes.LogPath + $result.ftpServer.logFile.period | Should -Be $DSCConfig.AllNodes.LogPeriod + $result.ftpServer.logFile.localTimeRollover | Should -Be $DSCConfig.AllNodes.LoglocalTimeRollover + + # Test DirectoryBrowseFlags + $result.ftpServer.directoryBrowse.showFlags.Split(',') | Should -BeIn $DSCConfig.AllNodes.DirectoryBrowseFlags + + # Test UserIsolation + $result.ftpServer.userIsolation.mode | Should -Be $DSCConfig.AllNodes.UserIsolation + } + } + + Describe "$($script:DSCResourceName)_Absent" { + #region DEFAULT TESTS + It 'Should compile without throwing' { + { + Invoke-Expression -Command "$($script:DSCResourceName)_Absent -ConfigurationData `$DSCConfig -OutputPath `$TestDrive" + 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 + } + + It 'should return True when calling Test-DscConfiguration' { + $result = Test-DscConfiguration + + $result | Should -Be $true + } + #endregion + + It 'Should remove the FTP site' -test { + Invoke-Expression -Command "$($script:DSCResourceName)_Absent -ConfigurationData `$DSCConfg -OutputPath `$TestDrive" + + # Build results to test + $result = Get-Website -Name $DSCConfig.AllNodes.Name + + # Test FTP Site is removed + $result | Should BeNullOrEmpty + } + } +} + +finally +{ + #region FOOTER + Restore-WebConfiguration -Name $tempName + Remove-WebConfigurationBackup -Name $tempName + Remove-LocalUser -InputObject $mockUser + Restore-TestEnvironment -TestEnvironment $TestEnvironment + #endregion +} diff --git a/Tests/Integration/MSFT_FTP.config.ps1 b/Tests/Integration/MSFT_FTP.config.ps1 new file mode 100644 index 000000000..a232f1dfe --- /dev/null +++ b/Tests/Integration/MSFT_FTP.config.ps1 @@ -0,0 +1,117 @@ +#requires -Version 4 + +configuration MSFT_FTP_Present +{ + param( + + [Parameter(Mandatory = $true)] + [String]$CertificateThumbprint + + ) + + Import-DscResource -ModuleName xWebAdministration + + Node $AllNodes.NodeName + { + FTP FTPSite + { + Ensure = 'Present' + Name = $Node.Name + ApplicationPool = $Node.ApplicationPool + PhysicalPath = $Node.PhysicalPath + PhysicalPathAccessAccount = $Node.PhysicalPathAccessAccount + PhysicalPathAccessPass = $Node.PhysicalPathAccessPass + State = $Node.State + AuthenticationInfo = ` + MSFT_FTPAuthenticationInformation + { + Anonymous = $Node.AuthenticationInfoAnonymous + Basic = $Node.AuthenticationInfoBasic + } + AuthorizationInfo = @( + MSFT_FTPAuthorizationInformation + { + AccessType = $Node.AuthorizationInfoAccessType1 + Users = $Node.AuthorizationInfoUsers1 + Roles = '' + Permissions = $Node.AuthorizationInfoPermissions1 + }; + MSFT_FTPAuthorizationInformation + { + AccessType = $Node.AuthorizationInfoAccessType1 + Users = $Node.AuthorizationInfoUsers2 + Roles = '' + Permissions = $Node.AuthorizationInfoPermissions3 + }; + MSFT_FTPAuthorizationInformation + { + AccessType = $Node.AuthorizationInfoAccessType2 + Users = $Node.AuthorizationInfoUsers3 + Roles = '' + Permissions = $Node.AuthorizationInfoPermissions1 + }; + MSFT_FTPAuthorizationInformation + { + AccessType = $Node.AuthorizationInfoAccessType1 + Users = '' + Roles = $Node.AuthorizationInfoRoles + Permissions = $Node.AuthorizationInfoPermissions1 + }; + MSFT_FTPAuthorizationInformation + { + AccessType = $Node.AuthorizationInfoAccessType2 + Users = '' + Roles = $Node.AuthorizationInfoRoles + Permissions = $Node.AuthorizationInfoPermissions2 + }) + BindingInfo = ` + MSFT_FTPBindingInformation + { + Protocol = $Node.BindingInfoProtocol + Port = $Node.BindingInfoPort + HostName = $Node.BindingInfoHostName + } + SslInfo = ` + MSFT_FTPSslInformation + { + ControlChannelPolicy = $Node.SslInfoControlChannelPolicy + DataChannelPolicy = $Node.SslInfoDataChannelPolicy + RequireSsl128 = $Node.SslInfoRequireSsl128 + CertificateThumbprint = $CertificateThumbprint + CertificateStoreName = $Node.SslInfoCertificateStoreName + } + FirewallIPaddress = $Node.FirewallIPaddress + StartingDataChannelPort = $Node.StartingDataChannelPort + EndingDataChannelPort = $Node.EndingDataChannelPort + GreetingMessage = $Node.GreetingMessage + ExitMessage = $Node.ExitMessage + BannerMessage = $Node.BannerMessage + MaxClientsMessage = $Node.MaxClientsMessage + SuppressDefaultBanner = $Node.SuppressDefaultBanner + AllowLocalDetailedErrors = $Node.AllowLocalDetailedErrors + ExpandVariablesInMessages = $Node.ExpandVariablesInMessages + LogPath = $Node.LogPath + LogFlags = $Node.LogFlags + LogPeriod = $Node.LogPeriod + LoglocalTimeRollover = $Node.LoglocalTimeRollover + DirectoryBrowseFlags = $Node.DirectoryBrowseFlags + UserIsolation = $Node.UserIsolation + } + } +} + +configuration MSFT_FTP_Absent +{ + + Import-DscResource -ModuleName xWebAdministration + + Node $AllNodes.NodeName + { + FTP FTPSite + { + Ensure = 'Absent' + Name = $Node.Name + + } + } +} diff --git a/Tests/Integration/MSFT_FTP.config.psd1 b/Tests/Integration/MSFT_FTP.config.psd1 new file mode 100644 index 000000000..7da7b245a --- /dev/null +++ b/Tests/Integration/MSFT_FTP.config.psd1 @@ -0,0 +1,49 @@ +#requires -Version 1 +@{ + AllNodes = @( + @{ + NodeName = 'LocalHost' + PSDscAllowPlainTextPassword = $true + Name = 'ftp' + State = 'Started' + ApplicationPool = 'DefaultAppPool' + PhysicalPath = 'C:\inetpub\ftproot' + PhysicalPathAccessAccount = 'mockFtpUser' + PhysicalPathAccessPass = 'P@$$w0rdP@55wOrd' + AuthenticationInfoAnonymous = $false + AuthenticationInfoBasic = $true + AuthorizationInfoAccessType1 = 'Allow' + AuthorizationInfoAccessType2 = 'Deny' + AuthorizationInfoUsers1 = 'User1' + AuthorizationInfoUsers2 = '*' + AuthorizationInfoUsers3 = '?' + AuthorizationInfoRoles = 'Group1' + AuthorizationInfoPermissions1 = 'Read' + AuthorizationInfoPermissions2 = 'Write' + AuthorizationInfoPermissions3 = 'Read,Write' + BindingInfoProtocol = 'ftp' + BindingInfoPort = '21' + BindingInfoHostName = 'ftp.server' + SslInfoControlChannelPolicy = 'SslAllow' + SslInfoDataChannelPolicy = 'SslAllow' + SslInfoRequireSsl128 = $true + SslInfoCertificateStoreName = 'My' + FirewallIPaddress = '10.0.0.10' + StartingDataChannelPort = 10500 + EndingDataChannelPort = 10550 + GreetingMessage = 'Greetings, %UserName%!' + ExitMessage = 'Bye, %UserName%!' + BannerMessage = "%UserName%, you've been watched.." + MaxClientsMessage = 'Sorry, %UserName%, try to connect again in an hour.' + SuppressDefaultBanner = $false + AllowLocalDetailedErrors = $true + ExpandVariablesInMessages = $true + LogPath = 'C:\inetpub\logs' + LogFlags = @('Date','Time','ClientIP','UserName','ServerIP','Method','UriStem') + LogPeriod = 'Hourly' + LoglocalTimeRollover = $true + DirectoryBrowseFlags = @('StyleUnix','LongDate','DisplayAvailableBytes','DisplayVirtualDirectories') + UserIsolation = 'IsolateAllDirectories' + } + ) +} diff --git a/Tests/Unit/Helper.Tests.ps1 b/Tests/Unit/Helper.Tests.ps1 index 47f78fbc8..15e246fd7 100644 --- a/Tests/Unit/Helper.Tests.ps1 +++ b/Tests/Unit/Helper.Tests.ps1 @@ -1,4 +1,4 @@ -$script:ModuleName = 'Helper' +$script:ModuleName = 'Helper' $script:DSCModuleName = 'xWebAdministration' #region HEADER @@ -18,7 +18,21 @@ Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\TestHelper\C # Begin Testing try { + #region Pester Tests InModuleScope $script:ModuleName { + $script:DSCResourceName = 'Helper' + + Describe "$DSCResourceName\Assert-Module" { + + Context 'WebAdminstration module is not installed' { + + Mock -CommandName Get-Module -MockWith { return $null } + + It 'Should throw an error' { + { Assert-Module } | Should Throw + } + } + } Describe "$DSCResourceName\Find-Certificate" { @@ -135,397 +149,397 @@ try } Context 'Thumbprint only is passed and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -Thumbprint $validThumbprint } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'Thumbprint only is passed and matching certificate does not exist' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -Thumbprint $nocertThumbprint } | Should Not Throw } - It 'should return null' { + It 'Should return null' { $script:result | Should BeNullOrEmpty } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'FriendlyName only is passed and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -FriendlyName $certFriendlyName } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'FriendlyName only is passed and matching certificate does not exist' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -FriendlyName 'Does Not Exist' } | Should Not Throw } - It 'should return null' { + It 'Should return null' { $script:result | Should BeNullOrEmpty } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'Subject only is passed and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -Subject $certSubject } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'Subject only is passed and matching certificate does not exist' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -Subject 'CN=Does Not Exist' } | Should Not Throw } - It 'should return null' { + It 'Should return null' { $script:result | Should BeNullOrEmpty } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'Subject only is passed and certificate with a different subject order exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -Subject $certSubjectLongReverse -Store 'LongSubject' } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $longThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'Subject only is passed and certificate subject without spaces exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -Subject $certSubjectNoSpace -Store 'LongSubject' } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $longThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'Issuer only is passed and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -Issuer $certSubject } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'Issuer only is passed and matching certificate does not exist' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -Issuer 'CN=Does Not Exist' } | Should Not Throw } - It 'should return null' { + It 'Should return null' { $script:result | Should BeNullOrEmpty } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'DNSName only is passed and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -DnsName $certDNSNames } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'DNSName only is passed in reversed order and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -DnsName $certDNSNamesReverse } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'DNSName only is passed with only one matching DNS name and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -DnsName $certDNSNames[0] } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'DNSName only is passed but an entry is missing and matching certificate does not exist' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -DnsName $certDNSNamesNoMatch } | Should Not Throw } - It 'should return null' { + It 'Should return null' { $script:result | Should BeNullOrEmpty } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'KeyUsage only is passed and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -KeyUsage $certKeyUsage } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'KeyUsage only is passed in reversed order and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -KeyUsage $certKeyUsageReverse } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'KeyUsage only is passed with only one matching DNS name and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -KeyUsage $certKeyUsage[0] } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'KeyUsage only is passed but an entry is missing and matching certificate does not exist' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -KeyUsage $certKeyUsageNoMatch } | Should Not Throw } - It 'should return null' { + It 'Should return null' { $script:result | Should BeNullOrEmpty } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'EnhancedKeyUsage only is passed and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -EnhancedKeyUsage $certEKU } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'EnhancedKeyUsage only is passed in reversed order and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -EnhancedKeyUsage $certEKUReverse } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'EnhancedKeyUsage only is passed with only one matching DNS name and matching certificate exists' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -EnhancedKeyUsage $certEKU[0] } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'EnhancedKeyUsage only is passed but an entry is missing and matching certificate does not exist' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -EnhancedKeyUsage $certEKUNoMatch } | Should Not Throw } - It 'should return null' { + It 'Should return null' { $script:result | Should BeNullOrEmpty } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'Thumbprint only is passed and matching certificate does not exist in the store' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -Thumbprint $validThumbprint -Store 'NoCert'} | Should Not Throw } - It 'should return null' { + It 'Should return null' { $script:result | Should BeNullOrEmpty } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'FriendlyName only is passed and both valid and expired certificates exist' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -FriendlyName $certFriendlyName -Store 'TwoCerts' } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $validThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'FriendlyName only is passed and only expired certificates exist' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -FriendlyName $certFriendlyName -Store 'Expired' } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result | Should BeNullOrEmpty } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } Context 'FriendlyName only is passed and only expired certificates exist but allowexpired passed' { - It 'should not throw exception' { + It 'Should not throw exception' { { $script:result = Find-Certificate -FriendlyName $certFriendlyName -Store 'Expired' -AllowExpired:$true } | Should Not Throw } - It 'should return expected certificate' { + It 'Should return expected certificate' { $script:result.Thumbprint | Should Be $expiredThumbprint } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 Assert-MockCalled -CommandName Get-ChildItem -Exactly -Times 1 } } } - Describe 'Get-LocalizedData' { + Describe "$DSCResourceName\Get-LocalizedData" { $mockTestPath = { return $mockTestPathReturnValue } @@ -570,7 +584,1974 @@ try Assert-VerifiableMock } - } + + Describe "$DSCResourceName\Confirm-UniqueServiceAutoStartProviders" { + + $MockParameters = @{ + Name = 'MockServiceAutoStartProvider' + Type = 'MockApplicationType' + } + + $GetWebConfigurationOutput = @( + @{ + SectionPath = 'MockSectionPath' + PSPath = 'MockPSPath' + Collection = @( + [PSCustomObject]@{Name = 'MockServiceAutoStartProvider' ;Type = 'MockApplicationType'} + ) + } + ) + + Context 'Expected behavior' { + + Mock -CommandName Get-WebConfiguration -MockWith {return $GetWebConfigurationOutput} + + It 'Should not throw an error' { + {Confirm-UniqueServiceAutoStartProviders -ServiceAutoStartProvider $MockParameters.Name -ApplicationType $MockParameters.Type} | + Should Not Throw + } + + It 'Should call Get-WebConfiguration once' { + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + } + } + + Context 'Conflicting Global Property' { + + Mock -CommandName Get-WebConfiguration -MockWith {return $GetWebConfigurationOutput} + + It 'Should return Throw' { + $ErrorId = 'ServiceAutoStartProviderFailure' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation + $ErrorMessage = $LocalizedData.ErrorWebsiteTestAutoStartProviderFailure + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + {Confirm-UniqueServiceAutoStartProviders -ServiceAutoStartProvider $MockParameters.Name -ApplicationType 'MockApplicationType2'} | + Should Throw $ErrorRecord + } + } + + Context 'ServiceAutoStartProvider does not exist' { + + Mock -CommandName Get-WebConfiguration -MockWith {return $null} + + It 'Should return False' { + Confirm-UniqueServiceAutoStartProviders -ServiceAutoStartProvider $MockParameters.Name -ApplicationType $MockParameters.Type | + Should Be $false + } + } + + Context 'ServiceAutoStartProvider does exist' { + + Mock -CommandName Get-WebConfiguration -MockWith {return $GetWebConfigurationOutput} + + It 'Should return True' { + Confirm-UniqueServiceAutoStartProviders -ServiceAutoStartProvider $MockParameters.Name -ApplicationType $MockParameters.Type | + Should Be $true + } + } + } + + Describe "$DSCResourceName\Confirm-UniqueBinding" { + + $MockParameters = @{ + Name = 'MockSite' + } + + $testCases = @( + @{ + protocol = 'http' + bindingInformation1 = '*:80:' + bindingInformation2 = '*:8080:' + bindingInformation3 = '*:81:' + bindingInformation4 = '*:8081:' + } + @{ + protocol = 'ftp' + bindingInformation1 = '*:21:' + bindingInformation2 = '*:2121:' + bindingInformation3 = '*:2122:' + bindingInformation4 = '*:2123:' + } + ) + + Context 'Website does not exist' { + + Mock -CommandName Get-Website + + It 'Should throw the correct error' { + $ErrorId = 'WebsiteNotFound' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult + $ErrorMessage = $LocalizedData.ErrorWebsiteNotFound -f $MockParameters.Name + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { Confirm-UniqueBinding -Name $MockParameters.Name } | Should Throw $ErrorRecord + } + } + + foreach($testCase in $testCases) + { + Context "Expected behavior for $($testCase.protocol) protocol" { + + $GetWebsiteOutput = @( + @{ + Name = $MockParameters.Name + State = 'Stopped' + Bindings = @{ + Collection = @( + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation1 } + ) + } + } + ) + + Mock -CommandName Get-Website -MockWith { return $GetWebsiteOutput } + + It 'Should not throw an error' { + { Confirm-UniqueBinding -Name $MockParameters.Name } | Should Not Throw + } + + It 'Should call Get-Website twice' { + Assert-MockCalled -CommandName Get-Website -Exactly 2 + } + } + + Context "Bindings for $($testCase.protocol) protocol are unique" { + + $GetWebsiteOutput = @( + @{ + Name = $MockParameters.Name + State = 'Stopped' + Bindings = @{ + Collection = @( + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation1 } + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation2 } + ) + } + } + @{ + Name = 'MockSite2' + State = 'Stopped' + Bindings = @{ + Collection = @( + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation3 } + ) + } + } + @{ + Name = 'MockSite3' + State = 'Started' + Bindings = @{ + Collection = @( + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation4 } + ) + } + } + ) + + Mock -CommandName Get-Website -MockWith {return $GetWebsiteOutput} + + It 'Should return True' { + Confirm-UniqueBinding -Name $MockParameters.Name | Should Be $true + } + } + + Context "Bindings for $($testCase.protocol) protocol are not unique" { + + $GetWebsiteOutput = @( + @{ + Name = $MockParameters.Name + State = 'Stopped' + Bindings = @{ + Collection = @( + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation1 } + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation2 } + ) + } + } + @{ + Name = 'MockSite2' + State = 'Started' + Bindings = @{ + Collection = @( + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation1 } + ) + } + } + @{ + Name = 'MockSite3' + State = 'Started' + Bindings = @{ + Collection = @( + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation2 } + ) + } + } + ) + + Mock -CommandName Get-Website -MockWith {return $GetWebsiteOutput} + + It 'Should return False' { + Confirm-UniqueBinding -Name $MockParameters.Name | Should Be $false + } + } + + Context "One of the bindings for $($testCase.protocol) protocol is assigned to another website that is Stopped" { + + $GetWebsiteOutput = @( + @{ + Name = $MockParameters.Name + State = 'Stopped' + Bindings = @{ + Collection = @( + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation1 } + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation2 } + ) + } + } + @{ + Name = 'MockSite2' + State = 'Stopped' + Bindings = @{ + Collection = @( + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation1 } + ) + } + } + ) + + Mock -CommandName Get-Website -MockWith { return $GetWebsiteOutput } + + It 'Should return True if stopped websites are excluded' { + Confirm-UniqueBinding -Name $MockParameters.Name -ExcludeStopped | Should Be $true + } + + It 'Should return False if stopped websites are not excluded' { + Confirm-UniqueBinding -Name $MockParameters.Name | Should Be $false + } + } + + Context "One of the bindings for $($testCase.protocol) protocol is assigned to another website that is Started" { + + $GetWebsiteOutput = @( + @{ + Name = $MockParameters.Name + State = 'Stopped' + Bindings = @{ + Collection = @( + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation1 } + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation2 } + ) + } + } + @{ + Name = 'MockSite2' + State = 'Stopped' + Bindings = @{ + Collection = @( + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation1 } + ) + } + } + @{ + Name = 'MockSite3' + State = 'Started' + Bindings = @{ + Collection = @( + @{ protocol = $testCase.protocol; bindingInformation = $testCase.bindingInformation1 } + ) + } + } + ) + + Mock -CommandName Get-Website -MockWith { return $GetWebsiteOutput } + + It 'Should return False' { + Confirm-UniqueBinding -Name $MockParameters.Name -ExcludeStopped | Should Be $false + } + } + } + } + + Describe "$DSCResourceName\Format-IPAddressString" { + + Context 'Input value is not valid' { + + It 'Should throw an error' { + { Format-IPAddressString -InputString 'Invalid' } | Should Throw + } + } + + Context 'Input value is valid' { + + It 'Should return "*" when input value is null' { + Format-IPAddressString -InputString $null | Should Be '*' + } + + It 'Should return "*" when input value is empty' { + Format-IPAddressString -InputString '' | Should Be '*' + } + + It 'Should return normalized IPv4 address' { + Format-IPAddressString -InputString '192.10' | Should Be '192.0.0.10' + } + + It 'Should return normalized IPv6 address enclosed in square brackets' { + Format-IPAddressString ` + -InputString 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' | Should Be '[fe80::202:b3ff:fe1e:8329]' + } + } + } + + Describe "$DSCResourceName\Test-IPAddress" { + + Context 'Input value is not valid' { + + It 'Should throw an error' { + { Test-IPAddress -InputString '256.192.134.80' } | Should Throw + } + } + + Context 'Input value is valid' { + + It 'Should return IP address' { + (Test-IPAddress -InputString '127.0.0.1').IPAddressToString | Should Be '127.0.0.1' + } + } + } + + Describe "$DSCResourceName\Test-PortNumber" { + + Context 'Input value is not valid' { + + It 'Should not throw an error' { + {Test-PortNumber -InputString 'InvalidString'} | Should Not Throw + } + + It 'Should return False' { + Test-PortNumber -InputString 'InvalidString' | Should Be $false + } + + It 'Should return False when input value is null' { + Test-PortNumber -InputString $null | Should Be $false + } + + It 'Should return False when input value is empty' { + Test-PortNumber -InputString '' | Should Be $false + } + + It 'Should return False when input value is not between 1 and 65535' { + Test-PortNumber -InputString '100000' | Should Be $false + } + } + + Context 'Input value is valid' { + It 'Should return True' { + Test-PortNumber -InputString '443' | Should Be $true + } + } + } + + Describe "$DSCResourceName\Get-DefaultAuthenticationInfo" { + + $testCases = @('Website', 'Application', 'Ftp') + + Context 'Expected behavior' { + + It 'Should not throw an error' { + { Get-DefaultAuthenticationInfo -IisType Website}| + Should Not Throw + } + } + + foreach($testCase in $testCases) + { + Context "Get-DefaultAuthenticationInfo should produce a false CimInstance for $testCase" { + + It 'Should all be false' { + $result = Get-DefaultAuthenticationInfo -IisType $testCase + + foreach($auth in $result.CimInstanceProperties.Name) + { + $result.$auth | Should Be $false + } + } + } + } + } + + Describe "$DSCResourceName\Get-AuthenticationInfo" { + + $testCases = @( + @{ + MockParameters = @{Site = 'MockName'; IisType = 'Website'} + AuthCount = 4 + } + @{ + MockParameters = @{Site = 'MockName'; IisType = 'Application'; Application = 'MockApp'} + AuthCount = 4 + } + @{ + MockParameters = @{Site = 'MockName'; IisType = 'Ftp'} + AuthCount = 2 + } + ) + + foreach ($testCase in $testCases) + { + $MockParameters = $testCase.MockParameters + + Context "Expected behavior for $($MockParameters.IisType)" { + + Mock -CommandName Get-WebConfigurationProperty -MockWith { return @{ Value = $false } } + Mock -CommandName Get-ItemProperty -MockWith { return @{ Value = $false } } + + It 'Should not throw an error' { + { Get-AuthenticationInfo @MockParameters } | + Should Not Throw + } + + It "Should call expected mocks" { + Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly ($testCase.AuthCount * ($MockParameters.IisType -ne 'Ftp')) + Assert-MockCalled -CommandName Get-ItemProperty -Exactly ($testCase.AuthCount * ($MockParameters.IisType -eq 'Ftp')) + } + } + + Context "AuthenticationInfo is False for $($MockParameters.IisType)" { + + Mock -CommandName Get-WebConfigurationProperty -MockWith { return @{ Value = $false } } + Mock -CommandName Get-ItemProperty -MockWith { return @{ Value = $false } } + + $result = Get-AuthenticationInfo @MockParameters + + It 'Should all be false' { + $result.Anonymous | Should Be $false + $result.Basic | Should Be $false + + if ($MockParameters.IisType -ne 'Ftp') + { + $result.Digest | Should Be $false + $result.Windows | Should Be $false + } + } + + It "Should call expected mocks" { + Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly ($testCase.AuthCount * ($MockParameters.IisType -ne 'Ftp')) + Assert-MockCalled -CommandName Get-ItemProperty -Exactly ($testCase.AuthCount * ($MockParameters.IisType -eq 'Ftp')) + } + } + + Context "AuthenticationInfo is True for $($MockParameters.IisType)" { + + Mock -CommandName Get-WebConfigurationProperty -MockWith { return @{ Value = $true } } + Mock -CommandName Get-ItemProperty -MockWith { return @{ Value = $true } } + + $result = Get-AuthenticationInfo @MockParameters + + It 'Should all be true' { + $result.Anonymous | Should Be $true + $result.Basic | Should Be $true + + if ($MockParameters.IisType -ne 'Ftp') + { + $result.Digest | Should Be $true + $result.Windows | Should Be $true + } + } + + It "Should call expected mocks" { + Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly ($testCase.AuthCount * ($MockParameters.IisType -ne 'Ftp')) + Assert-MockCalled -CommandName Get-ItemProperty -Exactly ($testCase.AuthCount * ($MockParameters.IisType -eq 'Ftp')) + } + } + } + } + + Describe "$DSCResourceName\Test-AuthenticationEnabled" { + + $testCases = @( + @{Site = 'MockName'; IisType = 'Website'; Type = 'Basic'} + @{Site = 'MockName'; IisType = 'Application'; Type = 'Basic'; Application = 'MockApp'} + @{Site = 'MockName'; IisType = 'Ftp'; Type = 'Basic'} + ) + + foreach ($testCase in $testCases) + { + Context "Expected behavior for $($testCase.IisType)" { + + Mock -CommandName Get-WebConfigurationProperty -MockWith { return @{ Value = $false } } + Mock -CommandName Get-ItemProperty -MockWith { return @{ Value = $false } } + + It "Should not throw an error"{ + { Test-AuthenticationEnabled @testCase}| + Should Not Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly ($testCase.IisType -ne 'Ftp') + Assert-MockCalled -CommandName Get-ItemProperty -Exactly ($testCase.IisType -eq 'Ftp') + } + } + + Context "AuthenticationInfo is False for $($testCase.IisType)" { + + Mock -CommandName Get-WebConfigurationProperty -MockWith { return @{ Value = $false } } + Mock -CommandName Get-ItemProperty -MockWith { return @{ Value = $false } } + + It 'Should return false' { + Test-AuthenticationEnabled @testCase | Should be $false + } + + It 'Should call expected mocks' { + Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly ($testCase.IisType -ne 'Ftp') + Assert-MockCalled -CommandName Get-ItemProperty -Exactly ($testCase.IisType -eq 'Ftp') + } + } + + Context "AuthenticationInfo is True for $($testCase.IisType)" { + + Mock -CommandName Get-WebConfigurationProperty -MockWith { return @{ Value = $true } } + Mock -CommandName Get-ItemProperty -MockWith { return @{ Value = $true } } + + It 'Should all be true' { + Test-AuthenticationEnabled @testCase | Should be $true + } + + It 'Should call expected mocks' { + Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly ($testCase.IisType -ne 'Ftp') + Assert-MockCalled -CommandName Get-ItemProperty -Exactly ($testCase.IisType -eq 'Ftp') + } + } + } + } + + Describe "$DSCResourceName\Test-AuthenticationInfo" { + + $testCases = @( + @{ + Site = 'MockName' + IisType = 'Website' + AuthenticationInfo = ( + New-CimInstance -ClassName MSFT_xWebAuthenticationInformation -ClientOnly ` + -Property @{Anonymous=$false;Basic=$true;Digest=$false;Windows=$false} ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + ) + } + @{ + Site = 'MockName' + IisType = 'Application' + Application = 'MockApp' + AuthenticationInfo = ( + New-CimInstance -ClassName MSFT_xWebApplicationAuthenticationInformation -ClientOnly ` + -Property @{Anonymous=$false;Basic=$true;Digest=$false;Windows=$false} ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + ) + } + @{ + Site = 'MockName' + IisType = 'Ftp' + AuthenticationInfo = ( + New-CimInstance -ClassName MSFT_FTPAuthenticationInformation -ClientOnly ` + -Property @{Anonymous=$false;Basic=$true} ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + ) + } + ) + + $truthyTestCases = @( + @{ + Site = 'MockName' + IisType = 'Website' + AuthenticationInfo = ( + New-CimInstance -ClassName MSFT_xWebAuthenticationInformation -ClientOnly ` + -Property @{Anonymous=$true;Basic=$true;Digest=$true;Windows=$true} ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + ) + } + @{ + Site = 'MockName' + IisType = 'Application' + Application = 'MockApp' + AuthenticationInfo = ( + New-CimInstance -ClassName MSFT_xWebApplicationAuthenticationInformation -ClientOnly ` + -Property @{Anonymous=$true;Basic=$true;Digest=$true;Windows=$true} ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + ) + } + @{ + Site = 'MockName' + IisType = 'Ftp' + AuthenticationInfo = ( + New-CimInstance -ClassName MSFT_FTPAuthenticationInformation -ClientOnly ` + -Property @{Anonymous=$true;Basic=$true} ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + ) + } + ) + + foreach($testCase in $testCases) + { + Context "Expected behavior for $($testCase.IisType)" { + + Mock -CommandName Get-WebConfigurationProperty -MockWith { return @{ Value = $false } } + Mock -CommandName Get-ItemProperty -MockWith { return @{ Value = $false } } + + It 'Should not throw an error' { + { Test-AuthenticationInfo @testCase }| + Should Not Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly (2 * ($testCase.IisType -ne 'Ftp')) + Assert-MockCalled -CommandName Get-ItemProperty -Exactly (2 * ($testCase.IisType -eq 'Ftp')) + } + } + + Context "Return False when AuthenticationInfo is not correct for $($testCase.IisType)" { + + Mock -CommandName Get-WebConfigurationProperty -MockWith { return @{ Value = $false } } + Mock -CommandName Get-ItemProperty -MockWith { return @{ Value = $false } } + + It 'Should return false' { + Test-AuthenticationInfo @testCase | Should be $false + } + + It 'Should call expected mocks' { + Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly (2 * ($testCase.IisType -ne 'Ftp')) + Assert-MockCalled -CommandName Get-ItemProperty -Exactly (2 * ($testCase.IisType -eq 'Ftp')) + } + } + } + + foreach($truthyTestCase in $truthyTestCases) + { + Context "Return True when AuthenticationInfo is correct for $($truthyTestCase.IisType)" { + + Mock -CommandName Get-WebConfigurationProperty -MockWith { return @{ Value = $true }} + Mock -CommandName Get-ItemProperty -MockWith { return @{ Value = $true } } + + It 'Should return true' { + Test-AuthenticationInfo @truthyTestCase | Should be $true + } + + It 'Should call expected mocks' { + Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly ($truthyTestCase.AuthenticationInfo.CimInstanceProperties.Count * ($truthyTestCase.IisType -ne 'Ftp')) + Assert-MockCalled -CommandName Get-ItemProperty -Exactly ($truthyTestCase.AuthenticationInfo.CimInstanceProperties.Count * ($truthyTestCase.IisType -eq 'Ftp')) + } + } + } + } + + Describe "$DSCResourceName\Set-Authentication" { + + $testCases = @( + @{Site = 'MockName'; IisType = 'Website'; Type = 'Basic'; Enabled = $true} + @{Site = 'MockName'; IisType = 'Application'; Type = 'Basic'; Enabled = $true; Application = 'MockApp'} + @{Site = 'MockName'; IisType = 'Ftp'; Type = 'Basic'; Enabled = $true} + ) + + foreach ($testCase in $testCases) + { + Context "Expected behavior for $($testCase.IisType)" { + + Mock -CommandName Set-WebConfigurationProperty + Mock -CommandName Set-ItemProperty + + It 'Should not throw an error' { + { Set-Authentication @testCase }| + Should Not Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly ($testCase.IisType -ne 'Ftp') + Assert-MockCalled -CommandName Set-ItemProperty -Exactly ($testCase.IisType -eq 'Ftp') + } + } + } + } + + Describe "$DSCResourceName\Set-AuthenticationInfo" { + + $websiteAuthenticationInfo = New-CimInstance ` + -ClassName MSFT_xWebAuthenticationInformation ` + -ClientOnly -Property @{Anonymous=$true;Basic=$false;Digest=$false;Windows=$false} ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + + $webApplicationAuthenticationInfo = New-CimInstance ` + -ClassName MSFT_xWebApplicationAuthenticationInformation ` + -ClientOnly -Property @{Anonymous=$true;Basic=$false;Digest=$false;Windows=$false} ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + + $ftpAuthenticationInfo = New-CimInstance ` + -ClassName MSFT_FTPAuthenticationInformation ` + -ClientOnly -Property @{Anonymous=$true;Basic=$false} ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + + $testCases = @( + @{Site = 'MockName'; IisType = 'Website'; AuthenticationInfo = $websiteAuthenticationInfo} + @{Site = 'MockName'; IisType = 'Application'; AuthenticationInfo = $webApplicationAuthenticationInfo; Application = 'MockApp'} + @{Site = 'MockName'; IisType = 'Ftp'; AuthenticationInfo = $ftpAuthenticationInfo} + ) + + foreach($testCase in $testCases) + { + Context "Expected behavior for $($testCase.IisType)" { + + Mock -CommandName Set-WebConfigurationProperty + Mock -CommandName Set-ItemProperty + + It 'Should not throw an error' { + { Set-AuthenticationInfo @testCase }| + Should Not Throw + } + + It "Should call expected mocks" { + Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly ($testCase.AuthenticationInfo.CimInstanceProperties.Count * ($testCase.IisType -ne 'Ftp')) + Assert-MockCalled -CommandName Set-ItemProperty -Exactly ($testCase.AuthenticationInfo.CimInstanceProperties.Count * ($testCase.IisType -eq 'Ftp')) + } + } + } + } + + Describe "$DSCResourceName\Compare-LogFlags" { + + $logFileOutput = @{ + logFile = @{ + logExtFileFlags = 'Date,Time,ClientIP,UserName,ServerIP' + } + } + + $testCases = @( + @{ + Type = 'WebSite' + MockParameters = @{ + Name = 'MockWebSite' + LogFlags = @('Date','Time','ClientIP','UserName','ServerIP') + } + MockOutput = $logFileOutput + } + @{ + Type = 'FtpSite' + MockParameters = @{ + Name = 'MockWebSite' + LogFlags = @('Date','Time','ClientIP','UserName','ServerIP') + FtpSite = $true + } + MockOutput = @{ + ftpServer = $logFileOutput + } + } + ) + + foreach($testCase in $testCases) + { + Context "Returns False when LogFlags are incorrect for $($testCase.Type)" { + + $MockParameters = $testCase.MockParameters.Clone() + $MockParameters.LogFlags = @('Date','Time','ClientIP','UserName','ServerIP','Method',` + 'UriStem','UriQuery','HttpStatus','Win32Status','TimeTaken',` + 'ServerPort','UserAgent','Referer','HttpSubStatus') + + Mock -CommandName Get-WebSite -MockWith { return $testCase.MockOutput } + + $result = Compare-LogFlags @MockParameters + + It 'Should return false' { + $result | Should be $false + } + } + + Context "Returns True when LogFlags are correct for $($testCase.Type)" { + + $MockParameters = $testCase.MockParameters.Clone() + + Mock -CommandName Get-WebSite -MockWith { return $testCase.MockOutput } + + $result = Compare-LogFlags @MockParameters + + It 'Should return true' { + $result | Should be $true + } + } + } + } + + Describe "$DSCResourceName\ConvertTo-CimLogCustomFields"{ + + $mockLogCustomFields = @( + @{ + LogFieldName = 'LogField1' + SourceName = 'Accept-Encoding' + SourceType = 'RequestHeader' + } + @{ + LogFieldName = 'LogField2' + SourceName = 'Warning' + SourceType = 'ResponseHeader' + } + ) + + Context 'Expected behavior'{ + $Result = ConvertTo-CimLogCustomFields -InputObject $mockLogCustomFields + + It 'Should return the LogFieldName' { + $Result[0].LogFieldName | Should Be $mockLogCustomFields[0].LogFieldName + $Result[0].LogFieldName | Should Be $mockLogCustomFields[0].LogFieldName + } + + It 'Should return the SourceName' { + $Result[0].SourceName | Should Be $mockLogCustomFields[0].SourceName + $Result[0].SourceName | Should Be $mockLogCustomFields[0].SourceName + } + + It 'Should return the SourceType' { + $Result[0].SourceType | Should Be $mockLogCustomFields[0].SourceType + $Result[0].SourceType | Should Be $mockLogCustomFields[0].SourceType + } + } + } + + Describe "$DSCResourceName\ConvertTo-CimBinding" { + + $IPs = @( + @{ Version = 'IPv4'; Address = '127.0.0.1' } + @{ Version = 'IPv6'; Address = '[0:0:0:0:0:0:0:1]' } + ) + + foreach($ip in $IPs) + { + $testCases = @( + @{ + InputObject = @{ protocol = 'http'; bindingInformation = "$($ip.Address):80:MockHostName1" } + MockOutput = @{ Protocol = 'http'; HostName = 'MockHostName1'; IPAddress = "$($ip.Address.Trim('[',']'))"; Port = 80 } + } + @{ + InputObject = @{ protocol = 'ftp'; bindingInformation = "$($ip.Address):21:MockHostName2" } + MockOutput = @{ Protocol = 'ftp'; HostName = 'MockHostName2'; IPAddress = "$($ip.Address.Trim('[',']'))"; Port = 21 } + } + @{ + InputObject = @{ + bindingInformation = "$($ip.Address):443:MockHostName3" + protocol = 'https' + certificateHash = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' + certificateStoreName = 'MY' + sslFlags = 1 + } + MockOutput = @{ + IPAddress = "$($ip.Address.Trim('[',']'))" + HostName = 'MockHostName3' + Port = 443 + Protocol = 'https' + CertificateThumbprint = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' + CertificateStoreName = 'MY' + SslFlags = 1 + } + } + ) + + foreach ($testCase in $testCases) + { + Context "$($ip.Version) address is passed and the protocol is $($testCase.InputObject.protocol)" { + + $Result = ConvertTo-CimBinding -InputObject $testCase.inputObject + + It "Should return the $($ip.Version) Address" { + $Result.IPAddress | Should Be $testCase.MockOutput.IPAddress + } + + It 'Should return the Protocol' { + $Result.Protocol | Should Be $testCase.MockOutput.Protocol + } + + It 'Should return the HostName' { + $Result.HostName | Should Be $testCase.MockOutput.HostName + } + + It 'Should return the Port' { + $Result.Port | Should Be $testCase.MockOutput.Port + } + + if ($testCase.InputObject.protocol -eq 'https') + { + It 'Should return the CertificateThumbprint' { + $Result.CertificateThumbprint | Should Be $testCase.MockOutput.CertificateThumbprint + } + + It 'Should return the CertificateStoreName' { + $Result.CertificateStoreName | Should Be $testCase.MockOutput.CertificateStoreName + } + + It 'Should return the SslFlags' { + $Result.SslFlags | Should Be $testCase.MockOutput.SslFlags + } + } + + if ($testCase.InputObject.protocol -eq 'ftp') + { + It 'Should not return properties for certificate binding' { + $Result.CimInstanceProperties.Name | Should -Not -BeIn @('CertificateThumbprint',` + 'CertificateStoreName', ` + 'SslFlags') + } + } + } + } + } + } + + Describe "$DSCResourceName\ConvertTo-WebBinding" -Tag 'ConvertTo' { + + $testCases = @( + @{ + protocol = 'http' + className = 'MSFT_xWebBindingInformation' + } + @{ + protocol = 'ftp' + className = 'MSFT_FTPBindingInformation' + } + ) + + foreach($testCase in $testCases) + { + Context "IP address is invalid for $($testCase.protocol) protocol" { + + $MockBindingInfo = @( + New-CimInstance -ClassName $testCase.className ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = $testCase.protocol + IPAddress = '127.0.0.256' + } + ) + + It 'Should throw the correct error' { + $ErrorId = 'WebBindingInvalidIPAddress' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument + $ErrorMessage = $LocalizedData.ErrorWebBindingInvalidIPAddress -f $MockBindingInfo.IPAddress, 'Exception calling "Parse" with "1" argument(s): "An invalid IP address was specified."' + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Throw $ErrorRecord + } + } + + Context "Port is invalid for $($testCase.protocol) protocol" { + + $MockBindingInfo = @( + New-CimInstance -ClassName $testCase.className ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = $testCase.protocol + Port = 0 + } + ) + + It 'Should throw the correct error' { + $ErrorId = 'WebBindingInvalidPort' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument + $ErrorMessage = $LocalizedData.ErrorWebBindingInvalidPort -f $MockBindingInfo.Port + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + {ConvertTo-WebBinding -InputObject $MockBindingInfo} | Should Throw $ErrorRecord + } + } + } + + Context "Protocol is not HTTPS but HTTP" { + + $MockBindingInfo = @( + New-CimInstance -ClassName 'MSFT_xWebBindingInformation' ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'http' + CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' + CertificateStoreName = 'WebHosting' + SslFlags = 1 + } + ) + + It 'Should ignore SSL properties' { + $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo + + $Result.certificateHash | Should Be '' + $Result.certificateStoreName | Should Be '' + $Result.sslFlags | Should Be 0 + } + } + + Context 'Expected behaviour for FTP' { + + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_FTPBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'ftp' + BindingInformation = 'NonsenseString' + IPAddress = '*' + Port = '21' + HostName = 'ftp01.contoso.com' + } + ) + + $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo + + It 'Should not return properties for certificate binding' { + $Result.CimInstanceProperties.Name | Should -Not -BeIn @('CertificateThumbprint',` + 'CertificateStoreName', ` + 'SslFlags') + } + + It 'Should return the correct Protocol value' { + $Result.protocol | Should Be 'ftp' + } + + It 'Should return the correct BindingInformation value' { + $Result.bindingInformation | Should Be '*:21:ftp01.contoso.com' + } + } + + Context "Expected behaviour for HTTPS" { + + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + BindingInformation = 'NonsenseString' + IPAddress = '*' + Port = 443 + HostName = 'web01.contoso.com' + CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' + CertificateStoreName = 'WebHosting' + SslFlags = 1 + } + ) + + $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo + + It 'Should return the correct Protocol value' { + $Result.protocol | Should Be 'https' + } + + It 'Should return the correct BindingInformation value' { + $Result.bindingInformation | Should Be '*:443:web01.contoso.com' + } + + It 'Should return the correct CertificateHash value' { + $Result.certificateHash | Should Be 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' + } + + It 'Should return the correct CertificateStoreName value' { + $Result.certificateStoreName | Should Be 'WebHosting' + } + + It 'Should return the correct SslFlags value' { + $Result.sslFlags | Should Be 1 + } + } + + Context "Port is not specified" { + + It 'Should set the default FTP port' { + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'ftp' + } + ) + + $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo + + $Result.bindingInformation | Should Be '*:21:' + } + + It 'Should set the default HTTP port' { + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'http' + } + ) + + $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo + + $Result.bindingInformation | Should Be '*:80:' + } + + It 'Should set the default HTTPS port' { + $MockBindingInfo = @( + New-CimInstance ` + -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' + } + ) + + $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo + + $Result.bindingInformation | Should Be '*:443:' + } + } + + Context 'Protocol is neither HTTP, HTTPS or FTP' { + + It 'Should throw an error if BindingInformation is not specified' { + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'net.tcp' + BindingInformation = '' + } + ) + + $ErrorId = 'WebBindingMissingBindingInformation' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument + $ErrorMessage = $LocalizedData.ErrorWebBindingMissingBindingInformation -f $MockBindingInfo.Protocol + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Throw $ErrorRecord + } + + It 'Should use BindingInformation and ignore IPAddress, Port, and HostName' { + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'net.tcp' + BindingInformation = '808:*' + IPAddress = '127.0.0.1' + Port = 80 + HostName = 'web01.contoso.com' + } + ) + + $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo + + $Result.BindingInformation | Should Be '808:*' + } + } + + Context 'Protocol is HTTPS and CertificateThumbprint contains the Left-to-Right Mark character' { + + $MockThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' + + $AsciiEncoding = [System.Text.Encoding]::ASCII + $UnicodeEncoding = [System.Text.Encoding]::Unicode + + $AsciiBytes = $AsciiEncoding.GetBytes($MockThumbprint) + $UnicodeBytes = [System.Text.Encoding]::Convert($AsciiEncoding, $UnicodeEncoding, $AsciiBytes) + $LrmCharBytes = $UnicodeEncoding.GetBytes([Char]0x200E) + + # Prepend the Left-to-Right Mark character to CertificateThumbprint + $MockThumbprintWithLrmChar = $UnicodeEncoding.GetString(($LrmCharBytes + $UnicodeBytes)) + + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + CertificateThumbprint = $MockThumbprintWithLrmChar + CertificateStoreName = 'MY' + } + ) + + It 'Input - CertificateThumbprint should contain the Left-to-Right Mark character' { + $MockBindingInfo[0].CertificateThumbprint -match '^\u200E' | Should Be $true + } + + It 'Output - certificateHash should not contain the Left-to-Right Mark character' { + $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo + + $Result.certificateHash -match '^\u200E' | Should Be $false + } + } + + Context 'Protocol is HTTPS and CertificateThumbprint is not specified' { + + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + CertificateThumbprint = '' + } + ) + + It 'Should throw the correct error' { + $ErrorId = 'WebBindingMissingCertificateThumbprint' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument + $ErrorMessage = $LocalizedData.ErrorWebBindingMissingCertificateThumbprint -f $MockBindingInfo.Protocol + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Throw $ErrorRecord + } + } + + Context 'Protocol is HTTPS and CertificateSubject is specified' { + + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + CertificateSubject = 'TestCertificate' + } + ) + + Mock Find-Certificate -MockWith { + return [PSCustomObject]@{ + Thumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' + } + } + + It 'Should not throw an error' { + { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Not Throw + } + + It 'Should return the correct thumbprint' { + $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo + + $Result.certificateHash | Should Be 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' + } + + It 'Should call Find-Certificate mock' { + Assert-MockCalled -CommandName Find-Certificate -Times 1 + } + } + + Context 'Protocol is HTTPS and full CN of CertificateSubject is specified' { + + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + CertificateSubject = 'CN=TestCertificate' + } + ) + + Mock Find-Certificate -MockWith { + return [PSCustomObject]@{ + Thumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' + } + } + + It 'Should not throw an error' { + { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Not Throw + } + + It 'Should return the correct thumbprint' { + $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo + + $Result.certificateHash | Should Be 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' + } + + It 'Should call Find-Certificate mock' { + Assert-MockCalled -CommandName Find-Certificate -Times 1 + } + } + + Context 'Protocol is HTTPS and invalid CertificateSubject is specified' { + + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + CertificateSubject = 'TestCertificate' + CertificateStoreName = 'MY' + } + ) + + Mock Find-Certificate + + It 'Should throw the correct error' { + $CertificateSubject = "CN=$($MockBindingInfo.CertificateSubject)" + $ErrorId = 'WebBindingInvalidCertificateSubject' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument + $ErrorMessage = $LocalizedData.ErrorWebBindingInvalidCertificateSubject -f $CertificateSubject, $MockBindingInfo.CertificateStoreName + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Throw $ErrorRecord + } + } + + Context 'Protocol is HTTPS and CertificateStoreName is not specified' { + + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' + CertificateStoreName = '' + } + ) + + It 'Should set CertificateStoreName to the default value' { + $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo + + $Result.certificateStoreName | Should Be 'MY' + } + } + + Context 'Protocol is HTTPS and HostName is not specified for use with Server Name Indication' { + + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + IPAddress = '*' + Port = 443 + HostName = '' + CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' + CertificateStoreName = 'WebHosting' + SslFlags = 1 + } + ) + + It 'Should throw the correct error' { + $ErrorId = 'WebBindingMissingSniHostName' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument + $ErrorMessage = $LocalizedData.ErrorWebBindingMissingSniHostName + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Throw $ErrorRecord + } + } + } + + Describe "$DSCResourceName\Update-WebsiteBinding" { + + $MockWebsite = @{ + Name = 'MockSite' + ItemXPath = "/system.applicationHost/sites/site[@name='MockSite']" + } + + $testCases = @( + @{ + MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + IPAddress = '*' + Port = 443 + HostName = '' + CertificateThumbprint = '5846A1B276328B1A32A30150858F6383C1F30E1F' + CertificateStoreName = 'MY' + SslFlags = 0 + } + ) + } + @{ + MockBindingInfo = @( + New-CimInstance -ClassName MSFT_FTPBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'ftp' + IPAddress = '*' + Port = 21 + HostName = '' + } + ) + } + ) + + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter { $Filter -eq '/system.applicationHost/sites/site' } ` + -MockWith { return $MockWebsite } -Verifiable + + Mock -CommandName Clear-WebConfiguration -Verifiable + + foreach($testCase in $testCases) + { + Context "Expected behavior for $($testCase.MockBindingInfo.Protocol) website" { + + Mock -CommandName Add-WebConfiguration + Mock -CommandName Set-WebConfigurationProperty + + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter { $Filter -eq "$($MockWebsite.ItemXPath)/bindings/binding[last()]" } ` + -MockWith { New-Module -AsCustomObject -ScriptBlock { function AddSslCertificate {} } } ` + -Verifiable + + Update-WebsiteBinding -Name $MockWebsite.Name -BindingInfo $testCases[0].MockBindingInfo + + It 'Should call all the mocks' { + Assert-VerifiableMock + Assert-MockCalled -CommandName Add-WebConfiguration -Exactly $testCase.MockBindingInfo.Count + Assert-MockCalled -CommandName Set-WebConfigurationProperty + } + } + + Context "$($testCase.MockBindingInfo.Protocol) website does not exist" { + + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter { $Filter -eq '/system.applicationHost/sites/site' } ` + -MockWith { return $null } + + It 'Should throw the correct error' { + $ErrorId = 'WebsiteNotFound' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult + $ErrorMessage = $LocalizedData.ErrorWebsiteNotFound -f $MockWebsite.Name + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { Update-WebsiteBinding ` + -Name $MockWebsite.Name ` + -BindingInfo $testCase.MockBindingInfo} | Should Throw $ErrorRecord + } + } + + Context "Error on adding a new binding to the $($testCase.MockBindingInfo.Protocol) website" { + + Mock -CommandName Add-WebConfiguration ` + -ParameterFilter { $Filter -eq "$($MockWebsite.ItemXPath)/bindings" } ` + -MockWith { throw } + + It 'Should throw the correct error' { + $ErrorId = 'WebsiteBindingUpdateFailure' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult + $ErrorMessage = $LocalizedData.ErrorWebsiteBindingUpdateFailure -f $MockWebsite.Name, 'ScriptHalted' + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { Update-WebsiteBinding ` + -Name $MockWebsite.Name ` + -BindingInfo $testCase.MockBindingInfo } | Should Throw $ErrorRecord + } + } + } + + Context 'Error on setting sslFlags attribute for HTTPS site' { + + Mock -CommandName Add-WebConfiguration + + Mock -CommandName Set-WebConfigurationProperty ` + -ParameterFilter { $Filter -eq "$($MockWebsite.ItemXPath)/bindings/binding[last()]" -and $Name -eq 'sslFlags' } ` + -MockWith { throw } + + It 'Should throw the correct error' { + $ErrorId = 'WebsiteBindingUpdateFailure' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult + $ErrorMessage = $LocalizedData.ErrorWebsiteBindingUpdateFailure -f $MockWebsite.Name, 'ScriptHalted' + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { Update-WebsiteBinding ` + -Name $MockWebsite.Name ` + -BindingInfo $testCases[0].MockBindingInfo } | Should Throw $ErrorRecord + } + } + + Context 'Error on adding SSL certificate for HTTPS site' { + + Mock -CommandName Add-WebConfiguration + + Mock -CommandName Set-WebConfigurationProperty + + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter { $Filter -eq "$($MockWebsite.ItemXPath)/bindings/binding[last()]" } ` + -MockWith { + New-Module -AsCustomObject -ScriptBlock { + function AddSslCertificate {throw} + } + } + + It 'Should throw the correct error' { + $ErrorId = 'WebBindingCertificate' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation + $ErrorMessage = $LocalizedData.ErrorWebBindingCertificate -f $testCases[0].MockBindingInfo.CertificateThumbprint, 'Exception calling "AddSslCertificate" with "2" argument(s): "ScriptHalted"' + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { Update-WebsiteBinding ` + -Name $MockWebsite.Name ` + -BindingInfo $testCases[0].MockBindingInfo } | Should Throw $ErrorRecord + } + } + } + + Describe "$DSCResourceName\Test-WebsiteBinding" { + + $MockFtpOutputBinding = @( + @{ + bindingInformation = '*:21:' + protocol = 'ftp' + } + ) + + $MockWebOutputBinding = @( + @{ + bindingInformation = '*:80:' + protocol = 'http' + certificateHash = '' + certificateStoreName = '' + sslFlags = '0' + } + ) + + $MockWebHttpsOutputBinding = @( + @{ + bindingInformation = '*:443:' + protocol = 'https' + certificateHash = 'B30F3184A831320382C61EFB0551766321FA88A5' + certificateStoreName = 'MY' + sslFlags = '0' + } + ) + + $testCases = @( + @{ + MockWebsite = @{ + Name = 'MockName' + Bindings = @{ + Collection = @( + $MockFtpOutputBinding + ) + } + } + BindingInfo = @( + New-CimInstance -ClassName MSFT_FTPBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'ftp' + IPAddress = '*' + Port = 21 + HostName = '' + } + ) + } + @{ + MockWebsite = @{ + Name = 'MockName' + Bindings = @{ + Collection = @( + $MockWebOutputBinding + ) + } + } + BindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'http' + IPAddress = '*' + Port = 80 + HostName = '' + CertificateThumbprint = '' + CertificateStoreName = '' + SslFlags = 0 + } + ) + } + @{ + MockWebsite = @{ + Name = 'MockSite' + Bindings = @{ + Collection = @( + $MockWebHttpsOutputBinding + ) + } + } + BindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + IPAddress = '*' + Port = 443 + HostName = '' + CertificateThumbprint = 'B30F3184A831320382C61EFB0551766321FA88A5' + CertificateStoreName = 'MY' + SslFlags = 0 + } + ) + } + ) + + foreach($testCase in $testCases) + { + Mock -CommandName Get-WebSite -MockWith {return $testCase.MockWebsite} + + Context "Test-BindingInfo returns False for $($testCase.BindingInfo.Protocol) website" { + + It 'Should throw the correct error' { + + Mock -CommandName Test-BindingInfo -MockWith {return $false} + + $ErrorId = 'WebsiteBindingInputInvalidation' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult + $ErrorMessage = $LocalizedData.ErrorWebsiteBindingInputInvalidation -f $testCase.MockWebsite.Name + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { Test-WebsiteBinding ` + -Name $testCase.MockWebsite.Name ` + -BindingInfo $testCase.BindingInfo } | Should Throw $ErrorRecord + } + } + + Context "Bindings comparison throws an error for $($testCase.BindingInfo.Protocol) website" { + + $ErrorId = 'WebsiteCompareFailure' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult + $ErrorMessage = $LocalizedData.ErrorWebsiteCompareFailure -f $testCase.MockWebsite.Name, 'ScriptHalted' + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + It 'Should not return an error' { + { Test-WebsiteBinding ` + -Name $testCase.MockWebsite.Name ` + -BindingInfo $testCase.BindingInfo} | Should Not Throw $ErrorRecord + } + } + + Context "Port is different for $($testCase.BindingInfo.Protocol) website" { + + $BindingInfo = $testCase.BindingInfo[0].Clone() + $BindingInfo.Port = 8888 + + It 'Should return False' { + Test-WebsiteBinding ` + -Name $testCase.MockWebsite.Name ` + -BindingInfo $BindingInfo | Should Be $false + } + } + + Context "Protocol is different for $($testCase.BindingInfo.Protocol) website" { + + $BindingInfo = $testCase.BindingInfo[0].Clone() + $BindingInfo.Protocol = 'net.tcp' + $bindingProperty = [Microsoft.Management.Infrastructure.CimProperty]::Create('BindingInformation', ` + "$($testCase.BindingInfo.IPAddress):$($testCase.BindingInfo.Port):$($testCase.BindingInfo.HostName)", ` + "String", ` + "Key" + ) + $BindingInfo.CimInstanceProperties.Add($bindingProperty) + + It 'Should return False' { + Test-WebsiteBinding ` + -Name $testCase.MockWebsite.Name ` + -BindingInfo $BindingInfo -Verbose:$true | Should Be $false + } + } + + Context "IPAddress is different for $($testCase.BindingInfo.Protocol) website" { + + $BindingInfo = $testCase.BindingInfo[0].Clone() + $BindingInfo.IPAddress = '127.0.0.1' + + It 'Should return False' { + Test-WebsiteBinding ` + -Name $testCase.MockWebsite.Name ` + -BindingInfo $BindingInfo | Should Be $false + } + } + + Context "HostName is different for $($testCase.BindingInfo.Protocol) website" { + + $BindingInfo = $testCase.BindingInfo[0].Clone() + $BindingInfo.HostName = 'MockHostName' + + It 'Should return False' { + Test-WebsiteBinding ` + -Name $testCase.MockWebsite.Name ` + -BindingInfo $BindingInfo | Should Be $false + } + } + } + + Context 'CertificateThumbprint is different' { + + $BindingInfo = $testCases[2].BindingInfo[0].Clone() + $BindingInfo.CertificateThumbprint = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' + $MockWebsite = $testCases[2].MockWebsite.Clone() + + Mock -CommandName Get-WebSite -MockWith {return $MockWebsite} + + It 'Should return False' { + Test-WebsiteBinding ` + -Name $MockWebsite.Name ` + -BindingInfo $BindingInfo | Should Be $false + } + } + + Context 'CertificateStoreName is different' { + + $BindingInfo = $testCases[2].BindingInfo[0].Clone() + $BindingInfo.CertificateStoreName = 'WebHosting' + $MockWebsite = $testCases[2].MockWebsite.Clone() + + Mock -CommandName Get-WebSite -MockWith {return $MockWebsite} + + It 'Should return False' { + Test-WebsiteBinding ` + -Name $MockWebsite.Name ` + -BindingInfo $BindingInfo | Should Be $false + } + } + + Context 'CertificateStoreName is different and no CertificateThumbprint is specified' { + + $BindingInfo = $testCases[2].BindingInfo[0].Clone() + $BindingInfo.CertificateStoreName = 'WebHosting' + $BindingInfo.CertificateThumbprint = '' + $MockWebsite = $testCases[2].MockWebsite.Clone() + + Mock -CommandName Get-WebSite -MockWith {return $MockWebsite} + + $ErrorId = 'WebsiteBindingInputInvalidation' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult + $ErrorMessage = $LocalizedData.ErrorWebsiteBindingInputInvalidation -f $MockWebsite.Name + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + It 'Should throw the correct error' { + { Test-WebsiteBinding ` + -Name $MockWebsite.Name ` + -BindingInfo $BindingInfo} | Should Throw $ErrorRecord + } + } + + Context 'SslFlags is different' { + + $BindingInfo = $testCases[2].BindingInfo[0].Clone() + $BindingInfo.HostName = 'test.contoso.com' + $BindingInfo.SslFlags = 1 + + $MockWebsite = @{ + Name = 'MockSite' + Bindings = @{ + Collection = @() + } + } + + $MockWebsite.Bindings.Collection += $testCases[0].MockWebsite.Bindings.Collection[0].Clone() + $MockWebsite.Bindings.Collection[0].bindingInformation = "$($BindingInfo.IPAddress):$($BindingInfo.Port):$($BindingInfo.HostName)" + + Mock -CommandName Get-WebSite -MockWith {return $MockWebsite} + + It 'Should return False' { + Test-WebsiteBinding ` + -Name $MockWebsite.Name ` + -BindingInfo $BindingInfo -Verbose:$true | Should Be $false + } + } + + Context 'Bindings are identical' { + + $MockWebsite = $testCases[0].MockWebsite.Clone() + $MockWebsite.Bindings.Collection += $testCases[1].MockWebsite.Bindings.Collection[0].Clone() + $MockWebsite.Bindings.Collection += $testCases[2].MockWebsite.Bindings.Collection[0].Clone() + + $BindingInfo = @() + $BindingInfo += $testCases[0].BindingInfo[0].Clone() + $BindingInfo += $testCases[1].BindingInfo[0].Clone() + $BindingInfo += $testCases[2].BindingInfo[0].Clone() + + Mock -CommandName Get-WebSite -MockWith {return $MockWebsite} + + It 'Should return True' { + Test-WebsiteBinding ` + -Name $MockWebsite.Name ` + -BindingInfo $BindingInfo | Should Be $true + } + } + + Context 'Bindings are different' { + + $MockWebsite = $testCases[0].MockWebsite.Clone() + $MockWebsite.Bindings.Collection += $testCases[1].MockWebsite.Bindings.Collection[0].Clone() + + $BindingInfo = @() + $BindingInfo += $testCases[1].BindingInfo[0].Clone() + $BindingInfo += $testCases[2].BindingInfo[0].Clone() + + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + + It 'Should return False' { + Test-WebsiteBinding ` + -Name $MockWebsite.Name ` + -BindingInfo $BindingInfo | Should Be $false + } + } + } + + Describe "$DSCResourceName\Test-BindingInfo" { + + $ftpMockBindingInfo = New-CimInstance ` + -ClassName MSFT_FTPBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'ftp' + IPAddress = '*' + Port = 21 + HostName = 'test.contoso.com' + } + + $httpMockBindingInfo = New-CimInstance ` + -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'http' + IPAddress = '*' + Port = 80 + HostName = 'test.contoso.com' + } + + $httpsMockBindingInfo = New-CimInstance ` + -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + IPAddress = '*' + Port = 443 + HostName = 'test.contoso.com' + CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' + CertificateStoreName = 'WebHosting' + SslFlags = 1 + } + + Context 'BindingInfo is valid' { + + $MockBindingInfo = @( + $ftpMockBindingInfo + $httpMockBindingInfo + $httpsMockBindingInfo + ) + + It 'Should return True' { + Test-BindingInfo -BindingInfo $MockBindingInfo | Should Be $true + } + } + + Context 'BindingInfo contains multiple items with the same IPAddress, Port, and HostName combination' { + + $MockBindingInfo = @( + $httpMockBindingInfo + $httpMockBindingInfo + ) + + It 'Should return False' { + Test-BindingInfo -BindingInfo $MockBindingInfo | Should Be $false + } + } + + Context 'BindingInfo contains items that share the same Port but have different Protocols' { + + $ftpMockBindingInfo.Port = 8080 + $httpMockBindingInfo.Port = 8080 + $httpsMockBindingInfo.Port = 8080 + + $MockBindingInfo = @( + $ftpMockBindingInfo + $httpMockBindingInfo + $httpsMockBindingInfo + ) + + It 'Should return False' { + Test-BindingInfo -BindingInfo $MockBindingInfo | Should Be $false + } + } + + Context 'BindingInfo contains multiple items with the same Protocol and BindingInformation combination' { + + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'net.tcp' + BindingInformation = '808:*' + } + + New-CimInstance -ClassName MSFT_xWebBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'net.tcp' + BindingInformation = '808:*' + } + ) + + It 'Should return False' { + Test-BindingInfo -BindingInfo $MockBindingInfo | Should Be $false + } + } + } + } + #endregion } finally { diff --git a/Tests/Unit/MSFT_FTP.tests.ps1 b/Tests/Unit/MSFT_FTP.tests.ps1 new file mode 100644 index 000000000..fda97b5b5 --- /dev/null +++ b/Tests/Unit/MSFT_FTP.tests.ps1 @@ -0,0 +1,2038 @@ +$script:DSCModuleName = 'xWebAdministration' +$script:DSCResourceName = 'MSFT_FTP' +$script:DSCHelperModuleName = 'Helper' + +#region HEADER +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) +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 (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +$TestEnvironment = Initialize-TestEnvironment -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit +#endregion + +# Begin Testing +try +{ + #region Pester Tests + InModuleScope -ModuleName $script:DSCResourceName -ScriptBlock { + $script:DSCResourceName = 'MSFT_FTP' + $script:DSCHelperModuleName = 'Helper' + + Describe "how $DSCResourceName\Get-TargetResource responds" { + + $MockLogOutput = @{ + directory = '%SystemDrive%\inetpub\logs\LogFiles' + logExtFileFlags = ('Date','Time','ClientIP','UserName','ServerIP','Method') + period = 'Daily' + truncateSize = '1048576' + localTimeRollover = 'False' + } + + $MockAuthenticationInfo = @( + New-CimInstance -ClassName MSFT_FTPAuthenticationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Anonymous = $true + Basic = $false + } + ) + + $MockAuthorizationInfo = @( + New-CimInstance -ClassName MSFT_FTPAuthorizationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + accessType = 'Allow' + users = 'User1' + roles = '' + permissions = 'Read' + } + ) + + $MockBindingInfo = @( + @{ + bindingInformation = '*:21:ftp.server' + protocol = 'ftp' + } + ) + + $MockSslInfo = @( + New-CimInstance -ClassName MSFT_FTPSslInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + ControlChannelPolicy = 'SslAllow' + DataChannelPolicy = 'SslAllow' + RequireSsl128 = 'True' + CertificateThumbprint = '' + CertificateStoreName = 'My' + } + ) + + $MockDefaultFirewallSupport = @{ + lowDataChannelPort = 0 + highDataChannelPort = 0 + } + + $MockMessageOutput = @{ + greetingMessage = 'Greetings, %UserName%!' + exitMessage = 'Bye, %UserName%!' + bannerMessage = "%UserName%, you've been watched.." + maxClientsMessage = 'Sorry, %UserName%, try to connect again in an hour.' + suppressDefaultBanner = $true + allowLocalDetailedErrors = $false + expandVariables = $true + } + + $MockFtpServerInfo = @( + @{ + userIsolation = @{ + mode = 'IsolateAllDirectories' + } + } + @{ + directoryBrowse = @{ + showFlags = 'LongDate' + } + } + @{ + firewallSupport = @{ + externalIp4Address = 10.0.0.10 + } + } + @{ + messages = $MockMessageOutput + } + @{ + logFile = $MockLogOutput + } + ) + + $MockWebsite = @{ + Name = 'MockFtp' + PhysicalPath = 'C:\NonExistent' + userName = 'mockUser' + password = 'mockPassword' + State = 'Started' + ApplicationPool = 'MockFtpPool' + AuthenticationInfo = $MockAuthenticationInfo + AuthorizationInfo = $MockAuthorizationInfo + SslInfo = $MockSslInfo + Bindings = @{Collection = @($MockBindingInfo)} + ftpServer = $MockFtpServerInfo + Count = 1 + } + + Context 'Website does not exist' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Get-Website + + $Result = Get-TargetResource -Name $MockWebsite.Name + + It 'Should return Absent' { + $Result.Ensure | Should Be 'Absent' + } + } + + Context 'There are multiple webftpsites with the same name' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Get-Website -MockWith { + return @( + @{Name = 'MockFtp'} + @{Name = 'MockFtp'} + ) + } + + It 'Should throw the correct error' { + $ErrorId = 'FtpSiteDiscoveryFailure' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult + $ErrorMessage = $LocalizedData.ErrorFtpSiteDiscoveryFailure -f 'MockFtp' + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + {Get-TargetResource -Name 'MockFtp'} | Should Throw $ErrorRecord + } + } + + Context 'Single website exists' { + + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Get-AuthenticationInfo { return $MockAuthenticationInfo } + Mock -CommandName Get-AuthorizationInfo { return $MockAuthorizationInfo } + Mock -CommandName Get-SslInfo { return $MockSslInfo } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter { $Filter -eq '/system.ftpServer/firewallSupport' } ` + -MockWith { return $MockDefaultFirewallSupport } + + $Result = Get-TargetResource -Name $MockWebsite.Name + + It 'Should call Get-Website once' { + Assert-MockCalled -CommandName Get-Website -Exactly 1 + } + + It 'Should return Ensure' { + $Result.Ensure | Should Be 'Present' + } + + It 'Should return Name' { + $Result.Name | Should Be $MockWebsite.Name + } + + It 'Should return PhysicalPath' { + $Result.PhysicalPath | Should Be $MockWebsite.PhysicalPath + } + + It 'Should return PhysicalPathAccessAccount' { + $Result.PhysicalPathAccessAccount | Should -Be $MockWebsite.userName + } + + It 'Should return PhysicalPathAccessPass' { + $Result.PhysicalPathAccessPass | Should -Be $MockWebsite.password + } + + It 'Should return State' { + $Result.State | Should Be $MockWebsite.State + } + + It 'Should return ApplicationPool' { + $Result.ApplicationPool | Should Be $MockWebsite.ApplicationPool + } + + It 'Should return AuthenticationInfo' { + $Result.AuthenticationInfo.CimInstanceProperties['Anonymous'].Value | Should Be $true + $Result.AuthenticationInfo.CimInstanceProperties['Basic'].Value | Should Be $false + } + + It 'Should return AuthorizationInfo' { + $Result.AuthorizationInfo.users | Should Be $MockAuthorizationInfo.Users + $Result.AuthorizationInfo.roles | Should BeNullOrEmpty + $Result.AuthorizationInfo.accessType | Should Be $MockAuthorizationInfo.accessType + $Result.AuthorizationInfo.permissions | Should Be $MockAuthorizationInfo.permissions + } + + It 'Should return SslInfo' { + $Result.SslInfo.ControlChannelPolicy | Should Be $MockSslInfo.ControlChannelPolicy + $Result.SslInfo.DataChannelPolicy | Should Be $MockSslInfo.DataChannelPolicy + $Result.SslInfo.RequireSsl128 | Should Be $MockSslInfo.RequireSsl128 + $Result.SslInfo.CertificateThumbprint | Should Be $MockSslInfo.CertificateThumbprint + $Result.SslInfo.CertificateStoreName | Should Be $MockSslInfo.CertificateStoreName + } + + It 'Should return BindingInfo' { + $Result.BindingInfo.HostName | Should Be 'ftp.server' + $Result.BindingInfo.Port | Should Be '21' + $Result.BindingInfo.Protocol | Should Be $MockBindingInfo.protocol + $Result.BindingInfo.IPAddress | Should Be '*' + } + + It 'Should return FirewallIPAddress' { + $Result.FirewallIPAddress | Should -Be $MockFtpServerInfo.ftpServer.firewallSupport.externalIp4Address + } + + It 'Should return StartingDataChannelPort' { + $Result.StartingDataChannelPort | Should -Be $MockDefaultFirewallSupport.lowDataChannelPort + } + + It 'Should return EndingDataChannelPort' { + $Result.EndingDataChannelPort | Should -Be $MockDefaultFirewallSupport.highDataChannelPort + } + + It 'Should return GreetingMessage' { + $Result.GreetingMessage | Should -Be $MockMessageOutput.greetingMessage + } + + It 'Should return ExitMessage' { + $Result.ExitMessage | Should -Be $MockMessageOutput.exitMessage + } + + It 'Should return BannerMessage' { + $Result.BannerMessage | Should -Be $MockMessageOutput.bannerMessage + } + + It 'Should return MaxClientsMessage' { + $Result.MaxClientsMessage | Should -Be $MockMessageOutput.maxClientsMessage + } + + It 'Should return SuppressDefaultBanner' { + $Result.SuppressDefaultBanner | Should -Be $MockMessageOutput.suppressDefaultBanner + } + + It 'Should return AllowLocalDetailedErrors' { + $Result.AllowLocalDetailedErrors | Should -Be $MockMessageOutput.allowLocalDetailedErrors + } + + It 'Should return ExpandVariablesInMessages' { + $Result.ExpandVariablesInMessages | Should -Be $MockMessageOutput.expandVariables + } + + It 'Should return LogPath' { + $Result.LogPath | Should Be $MockWebsite.ftpServer.logFile.directory + } + + It 'Should return LogFlags' { + $Result.LogFlags | Should -BeIn $MockWebsite.ftpServer.logFile.LogExtFileFlags + } + + It 'Should return LogPeriod' { + $Result.LogPeriod | Should Be $MockWebsite.ftpServer.logFile.period + } + + It 'Should return LogtruncateSize' { + $Result.LogtruncateSize | Should Be $MockWebsite.ftpServer.logFile.truncateSize + } + + It 'Should return LoglocalTimeRollover' { + $Result.LoglocalTimeRollover | Should Be $MockWebsite.ftpServer.logFile.localTimeRollover + } + + It 'Should return DirectoryBrowseFlags' { + $Result.DirectoryBrowseFlags | Should Be $MockFtpServerInfo.directoryBrowse.showFlags + } + + It 'Should return UserIsolation' { + $Result.UserIsolation | Should Be $MockFtpServerInfo.userIsolation.mode + } + } + } + + Describe "how $DSCResourceName\Test-TargetResource responds to Ensure = 'Absent'" { + + $MockWebsite = 'Ftp' + + Mock -CommandName Assert-Module -MockWith {} + + Context 'Ftp site does not exist' { + + Mock -CommandName Get-Website -MockWith { + return $null + } + + It 'Should return True' { + $Result = Test-TargetResource -Ensure 'Absent' -Name $MockWebsite + $Result | Should Be $true + } + } + + Context 'Ftp site exists' { + + Mock -CommandName Get-Website -MockWith { + return @{ Name = $MockWebsite } + } + + It 'Should return False' { + $Result = Test-TargetResource -Ensure 'Absent' -Name $MockWebsite + $Result | Should Be $false + } + } + } + + Describe "how $DSCResourceName\Test-TargetResource responds to Ensure = 'Present'" { + + $MockAuthenticationInfo = New-CimInstance ` + -ClassName MSFT_FTPAuthenticationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Anonymous = $true + Basic = $false + } + + $MockAuthorizationInfo = @( + New-CimInstance -ClassName MSFT_FTPAuthorizationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + accessType = 'Allow' + users = 'User1' + roles = '' + permissions = 'Read' + } + ) + + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_FTPBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'ftp' + Port = '21' + HostName = 'ftp.server' + } + ) + + $MockSslInfo = New-CimInstance ` + -ClassName MSFT_FTPSslInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + controlChannelPolicy = 'SslAllow' + dataChannelPolicy = 'SslAllow' + ssl128 = 'True' + serverCertHash = '' + serverCertStoreName = 'My' + } + + $MockLogOutput = @{ + directory = '%SystemDrive%\inetpub\logs\LogFiles' + logExtFileFlags = 'Date,Time,ClientIP,UserName,ServerIP,Method' + period = 'Daily' + truncateSize = '1048576' + localTimeRollover = 'False' + } + + $MockMessageOutput = @{ + greetingMessage = 'Greetings, %UserName%!' + exitMessage = 'Bye, %UserName%!' + bannerMessage = "%UserName%, you've been watched.." + maxClientsMessage = 'Sorry, %UserName%, try to connect again in an hour.' + suppressDefaultBanner = $true + allowLocalDetailedErrors = $false + expandVariables = $true + } + + $MockFtpServerInfo = @( + @{ + userIsolation = @{ + mode = 'IsolateAllDirectories' + } + } + @{ + directoryBrowse = @{ + showFlags = 'LongDate' + } + } + @{ + security = @{ + ssl = @( + New-Object -TypeName PSObject -Property @{ + serverCertHash = 'EF8D5381178A622886A30CBBB46BBA8F4AFAAC97' + serverCertStoreName = 'MY' + ssl128 = 'True' + controlChannelPolicy = 'SslAllow' + dataChannelPolicy = 'SslAllow' + } + ) + } + } + @{ + firewallSupport = @{ + externalIp4Address = '10.0.0.10' + } + } + @{ + messages = $MockMessageOutput + } + @{ + logfile = $MockLogOutput + } + ) + + $MockDefaultFirewallSupport = @{ + lowDataChannelPort = 0 + highDataChannelPort = 0 + } + + $MockParameters = @{ + Ensure = 'Present' + Name = 'MockFtp' + PhysicalPath = 'C:\NonExistent' + PhysicalPathAccessAccount = 'MockUser' + PhysicalPathAccessPass = 'MockPassword' + State = 'Stopped' + ApplicationPool = 'MockFtpPool' + AuthorizationInfo = $MockAuthorizationInfo + BindingInfo = $MockBindingInfo + SslInfo = $MockSslInfo + FirewallIPAddress = '192.168.0.20' + StartingDataChannelPort = 10550 + EndingDataChannelPort = 10600 + GreetingMessage = 'Mock hello' + ExitMessage = 'Mock exit' + BannerMessage = 'Mock banner' + MaxClientsMessage = 'Mock message max client' + SuppressDefaultBanner = $false + AllowLocalDetailedErrors = $true + ExpandVariablesInMessages = $false + LogPath = '%SystemDrive%\DifferentLogFiles' + LogFlags = @('Date','Time','ClientIP','UserName','ServerIP') + LogPeriod = 'Hourly' + LogTruncateSize = '2048570' + LoglocalTimeRollover = $true + DirectoryBrowseFlags = 'StyleUnix' + UserIsolation = 'StartInUsersDirectory' + } + + $MockWebsite = @{ + Name = 'MockFtp' + PhysicalPath = 'C:\Different' + userName = '' + password = '' + State = 'Started' + ApplicationPool = 'MockPoolDifferent' + AuthenticationInfo = $MockAuthenticationInfo + AuthorizationInfo = $MockAuthorizationInfo + Bindings = @{Collection = @($MockBindingInfo)} + ftpServer = $MockFtpServerInfo + Count = 1 + } + + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + + Context 'Website does not exist' { + + Mock -CommandName Get-Website + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check PhysicalPath is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check PhysicalPathAccessAccount is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-Website ` + -MockWith {return $MockWebsite} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPathAccessAccount $MockParameters.PhysicalPathAccessAccount + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check PhysicalPathAccessPass is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-Website ` + -MockWith {return $MockWebsite} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPathAccessPass $MockParameters.PhysicalPathAccessPass + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check State is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -State $MockParameters.State + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check ApplicationPool is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Name $MockParameters.Name ` + -Ensure $MockParameters.Ensure ` + -ApplicationPool $MockParameters.ApplicationPool + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check AuthenticationInfo is different' { + + Mock -CommandName Get-WebConfiguration + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-ItemProperty ` + -MockWith { return @{ Value = $false } } ` + -ParameterFilter { $Name -eq 'ftpServer.security.authentication.AnonymousAuthentication.enabled'} + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-ItemProperty ` + -MockWith { return @{ Value = $false } } ` + -ParameterFilter { $Name -eq 'ftpServer.security.authentication.BasicAuthentication.enabled' } + + $MockAuthenticationInfo = New-CimInstance -ClassName MSFT_xWebAuthenticationInformation ` + -ClientOnly ` + -Property @{ Anonymous=$true; Basic=$true } ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -AuthenticationInfo $MockAuthenticationInfo + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check AuthenticationInfo is different from default' { + + Mock -CommandName Get-WebConfiguration + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-ItemProperty ` + -MockWith { return @{ Value = $true } } ` + -ParameterFilter { $Name -eq 'ftpServer.security.authentication.AnonymousAuthentication.enabled'} + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-ItemProperty ` + -MockWith { return @{ Value = $false } } ` + -ParameterFilter { $Name -eq 'ftpServer.security.authentication.BasicAuthentication.enabled' } + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check BindingInfo is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-WebsiteBinding -MockWith {$false} + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Name $MockParameters.Name ` + -Ensure $MockParameters.Ensure ` + -BindingInfo $MockBindingInfo + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check AuthorizationInfo is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthorizationInfo -MockWith {return $false} + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -AuthorizationInfo $MockAuthorizationInfo + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check SslInfo is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Confirm-UniqueSslInfo -MockWith {return $false} + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -SslInfo $MockSslInfo + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check FirewallIPAddress is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -FirewallIPAddress $MockParameters.FirewallIPAddress + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check StartingDataChannelPort is different' { + + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter { $Filter -eq '/system.ftpServer/firewallSupport' } ` + -MockWith { return $MockDefaultFirewallSupport } + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -StartingDataChannelPort $MockParameters.StartingDataChannelPort + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check EndingDataChannelPort is different' { + + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter { $Filter -eq '/system.ftpServer/firewallSupport' } ` + -MockWith { return $MockDefaultFirewallSupport } + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -EndingDataChannelPort $MockParameters.EndingDataChannelPort + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check GreetingMessage is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Name $MockParameters.Name ` + -Ensure $MockParameters.Ensure ` + -GreetingMessage $MockParameters.GreetingMessage + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check ExitMessage is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Name $MockParameters.Name ` + -Ensure $MockParameters.Ensure ` + -ExitMessage $MockParameters.ExitMessage + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check BannerMessage is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Name $MockParameters.Name ` + -Ensure $MockParameters.Ensure ` + -BannerMessage $MockParameters.BannerMessage + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check MaxClientsMessage is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Name $MockParameters.Name ` + -Ensure $MockParameters.Ensure ` + -MaxClientsMessage $MockParameters.MaxClientsMessage + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check SuppressDefaultBanner is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Name $MockParameters.Name ` + -Ensure $MockParameters.Ensure ` + -SuppressDefaultBanner $MockParameters.SuppressDefaultBanner + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check AllowLocalDetailedErrors is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Name $MockParameters.Name ` + -Ensure $MockParameters.Ensure ` + -AllowLocalDetailedErrors $MockParameters.AllowLocalDetailedErrors + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check ExpandVariablesInMessages is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Name $MockParameters.Name ` + -Ensure $MockParameters.Ensure ` + -ExpandVariablesInMessages $MockParameters.ExpandVariablesInMessages + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check LogPath is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-Website ` + -MockWith {return $MockWebsite} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -LogPath $MockParameters.LogPath + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check LogFlags is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-Website ` + -MockWith {return $MockWebsite} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -LogFlags $MockParameters.LogFlags + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check LogPeriod is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-Website ` + -MockWith {return $MockWebsite} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -LogPeriod $MockParameters.LogPeriod + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check if LogPeriod is ignored with LogTruncateSize set' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-Website ` + -MockWith {return $MockWebsite} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -LogPeriod $MockParameters.LogPeriod ` + -LogTruncateSize 1048576 + + It 'Should return True' { + $Result | Should Be $true + } + } + + Context 'Check LogTruncateSize is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-Website ` + -MockWith {return $MockWebsite} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -LogTruncateSize $MockParameters.LogTruncateSize + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check LoglocalTimeRollover is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-Website ` + -MockWith {return $MockWebsite} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -LoglocalTimeRollover $MockParameters.LoglocalTimeRollover + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check DirectoryBrowseFlags is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -DirectoryBrowseFlags $MockParameters.DirectoryBrowseFlags + + It 'Should return False' { + $Result | Should Be $false + } + } + + Context 'Check UserIsolation is different' { + + Mock -CommandName Get-WebConfiguration + Mock -CommandName Test-AuthenticationInfo -MockWith {return $true} + + $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -UserIsolation $MockParameters.UserIsolation + + It 'Should return False' { + $Result | Should Be $false + } + } + } + + Describe "how $DSCResourceName\Set-TargetResource responds to Ensure = 'Present'" { + + $MockAuthenticationInfo = New-CimInstance ` + -ClassName MSFT_FTPAuthenticationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Anonymous = $true + Basic = $false + } + + $MockAuthorizationInfo = @( + New-CimInstance -ClassName MSFT_FTPAuthorizationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + accessType = 'Allow' + users = 'User1' + roles = '' + permissions = 'Read' + } + ) + + $MockBindingInfo = @( + New-CimInstance -ClassName MSFT_FTPBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'ftp' + Port = '21' + HostName = 'ftp.server' + } + ) + + $MockSslInfo = New-CimInstance ` + -ClassName MSFT_FTPSslInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + controlChannelPolicy = 'SslAllow' + dataChannelPolicy = 'SslAllow' + ssl128 = 'True' + serverCertHash = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234567890' + serverCertStoreName = 'My' + } + + $MockParameters = @{ + Ensure = 'Present' + Name = 'MockFtp' + PhysicalPath = 'C:\NonExistent' + PhysicalPathAccessAccount = 'MockUser' + PhysicalPathAccessPass = 'MockPassword' + State = 'Started' + ApplicationPool = 'MockFtpPool' + AuthenticationInfo = $MockAuthenticationInfo + AuthorizationInfo = $MockAuthorizationInfo + BindingInfo = $MockBindingInfo + SslInfo = $MockSslInfo + FirewallIPAddress = '' + StartingDataChannelPort = 0 + EndingDataChannelPort = 0 + GreetingMessage = 'Mock hello' + ExitMessage = 'Mock exit' + BannerMessage = 'Mock banner' + MaxClientsMessage = 'Mock message max client' + SuppressDefaultBanner = $false + AllowLocalDetailedErrors = $true + ExpandVariablesInMessages = $false + LogPath = '%SystemDrive%\LogFiles' + LogFlags = @('Date','Time','ClientIP','UserName','ServerIP','Method') + LogPeriod = 'Daily' + LoglocalTimeRollover = $true + DirectoryBrowseFlags = 'StyleUnix' + UserIsolation = 'StartInUsersDirectory' + } + + $DifferentMockLogOutput = @{ + directory = '%SystemDrive%\inetpub\logs\LogFiles' + logExtFileFlags = 'Date,Time,ClientIP,UserName,ServerIP' + period = 'Hourly' + truncateSize = '1048576' + localTimeRollover = 'False' + } + + $DifferentMockAuthenticationInfo = New-CimInstance ` + -ClassName MSFT_FTPAuthenticationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Anonymous = $true + Basic = $true + } + + $DifferentMockAuthorizationInfo = @( + New-CimInstance -ClassName MSFT_FTPAuthorizationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + accessType = 'Allow' + users = 'User1' + roles = '' + permissions = 'Read' + } + ) + + $DifferentMockBindingInfo = @( + New-CimInstance -ClassName MSFT_FTPBindingInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'ftp' + Port = '21' + HostName = 'ftp.server' + } + ) + + $DifferentMockSslInfo = New-CimInstance ` + -ClassName MSFT_FTPSslInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + controlChannelPolicy = 'SslAllow' + dataChannelPolicy = 'SslAllow' + ssl128 = 'True' + serverCertHash = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234567890' + serverCertStoreName = 'My' + } + + $MockMessageOutput = @{ + greetingMessage = 'Greetings, %UserName%!' + exitMessage = 'Bye, %UserName%!' + bannerMessage = "%UserName%, you've been watched.." + maxClientsMessage = 'Sorry, %UserName%, try to connect again in an hour.' + suppressDefaultBanner = $true + allowLocalDetailedErrors = $false + expandVariables = $true + } + + $DifferentMockFtpServerInfo = @( + @{ + userIsolation = @{ + mode = 'IsolateAllDirectories' + } + } + @{ + directoryBrowse = @{ + showFlags = 'LongDate' + } + } + @{ + security = @{ + ssl = @( + New-Object -TypeName PSObject -Property @{ + serverCertHash = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234567890' + serverCertStoreName = 'MY' + ssl128 = 'True' + controlChannelPolicy = 'SslAllow' + dataChannelPolicy = 'SslAllow' + } + ) + } + } + @{ + firewallSupport = @{ + externalIp4Address = 10.0.0.10 + } + } + @{ + messages = $MockMessageOutput + } + @{ + logfile = $DifferentMockLogOutput + } + ) + + $MockDefaultFirewallSupport = @{ + lowDataChannelPort = 10500 + highDataChannelPort = 10600 + } + + $MockFtpAuthorization = @{ + accessType = 'Allow' + users = '' + roles = 'User1' + permissions = 'Read' + } + + $MockWebsite = @{ + Name = 'MockFtp' + PhysicalPath = 'C:\Different' + userName = '' + password = '' + State = '' + ApplicationPool = 'DifferentMockFtpPool' + AuthenticationInfo = $DifferentMockAuthenticationInfo + AuthorizationInfo = $DifferentMockAuthorizationInfo + SslInfo = $DifferentMockSslInfo + Bindings = @{Collection = @($DifferentMockBindingInfo)} + ftpServer = $DifferentMockFtpServerInfo + Count = 1 + } + + Context 'All properties need to be updated and webftpsite must be started' { + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-ItemProperty ` + -MockWith { return @{ Value = $false } } ` + -ParameterFilter { $Name -eq 'ftpServer.security.authentication.AnonymousAuthentication.enabled' } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-ItemProperty ` + -MockWith { return @{ Value = $false } } ` + -ParameterFilter { $Name -eq 'ftpServer.security.authentication.BasicAuthentication.enabled' } + + Mock -CommandName Get-WebConfiguration ` + -MockWith { return $null } ` + -ParameterFilter { $filter -eq '/system.ftpServer/security/authorization' } + + Mock -CommandName Get-WebConfiguration ` + -MockWith { return $MockDefaultFirewallSupport } ` + -ParameterFilter { $Filter -eq '/system.ftpServer/firewallSupport' } + + Mock -ModuleName $DSCHelperModuleName -CommandName Set-Authentication + Mock -ModuleName $DSCHelperModuleName -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Set-WebConfigurationProperty + Mock -CommandName Set-ItemProperty + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Start-Website + Mock -CommandName New-Webftpsite -MockWith {return $MockWebsite} + Mock -CommandName Set-FTPAuthorization + Mock -CommandName Update-WebsiteBinding + Mock -CommandName Set-SslInfo + Mock -CommandName Confirm-UniqueSslInfo { return $false } + + Set-TargetResource @MockParameters + + It 'Should call all the mocks' { + Assert-MockCalled -CommandName Set-ItemProperty -Exactly 18 + Assert-MockCalled -CommandName Start-Website -Exactly 1 + Assert-MockCalled -CommandName Set-SslInfo -Exactly 1 + Assert-MockCalled -CommandName Set-FTPAuthorization -Exactly 1 + Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 2 + Assert-MockCalled -ModuleName $DSCHelperModuleName ` + -CommandName Get-Website -Exactly 2 + Assert-MockCalled -CommandName Update-WebsiteBinding -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName ` + -CommandName Set-Authentication -Exactly 2 + } + } + + Context 'All properties need to be updated and webftpsite must be stopped' { + + $MockParameters = $MockParameters.Clone() + $MockParameters.State = 'Stopped' + + $MockWebsite = $MockWebsite.Clone() + $MockWebsite.State = 'Started' + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-ItemProperty ` + -MockWith { return @{ Value = $false } } ` + -ParameterFilter { $Name -eq 'ftpServer.security.authentication.AnonymousAuthentication.enabled' } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-ItemProperty ` + -MockWith { return @{ Value = $false } } ` + -ParameterFilter { $Name -eq 'ftpServer.security.authentication.BasicAuthentication.enabled' } + + Mock -CommandName Get-WebConfiguration ` + -MockWith { return $null } ` + -ParameterFilter { $filter -eq '/system.ftpServer/security/authorization' } + + Mock -CommandName Get-WebConfiguration ` + -MockWith { return $MockDefaultFirewallSupport } ` + -ParameterFilter { $Filter -eq '/system.ftpServer/firewallSupport' } + + Mock -CommandName Set-FTPAuthorization + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -ModuleName $DSCHelperModuleName -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Set-ItemProperty + Mock -CommandName Set-WebConfigurationProperty + Mock -ModuleName $DSCHelperModuleName -CommandName Set-Authentication + Mock -CommandName Stop-Website + Mock -CommandName New-Webftpsite -MockWith {return $MockWebsite} + Mock -CommandName Set-FTPAuthorization + Mock -CommandName Update-WebsiteBinding + Mock -CommandName Set-SslInfo + Mock -CommandName Confirm-UniqueSslInfo { return $false } + + Set-TargetResource @MockParameters + + It 'Should call all the mocks' { + Assert-MockCalled -CommandName Set-ItemProperty -Exactly 18 + Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 2 + Assert-MockCalled -CommandName Stop-Website -Exactly 1 + Assert-MockCalled -CommandName Set-SslInfo -Exactly 1 + Assert-MockCalled -CommandName Set-FTPAuthorization -Exactly 1 + Assert-MockCalled -CommandName Update-WebsiteBinding -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName ` + -CommandName Set-Authentication -Exactly 2 + } + } + + Context 'webftpsite does not exist' { + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-ItemProperty ` + -MockWith { return @{ Value = $false } } ` + -ParameterFilter { $Name -eq 'ftpServer.security.authentication.AnonymousAuthentication.enabled' } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-ItemProperty ` + -MockWith { return @{ Value = $false } } ` + -ParameterFilter { $Name -eq 'ftpServer.security.authentication.BasicAuthentication.enabled' } + + Mock -CommandName Get-WebConfiguration ` + -MockWith { return $null } ` + -ParameterFilter { $filter -eq '/system.ftpServer/security/authorization' } + + Mock -CommandName Get-WebConfiguration ` + -MockWith { return $MockDefaultFirewallSupport } ` + -ParameterFilter { $Filter -eq '/system.ftpServer/firewallSupport' } + + Mock -CommandName Get-Website { return $null } + Mock -ModuleName $DSCHelperModuleName -CommandName Get-Website -MockWith {return $null} + Mock -CommandName Set-FTPAuthorization + Mock -ModuleName $DSCHelperModuleName -CommandName Set-Authentication + Mock -CommandName Set-ItemProperty + Mock -CommandName Set-WebConfigurationProperty + Mock -CommandName Start-Website + Mock -CommandName New-Webftpsite -MockWith {return $MockWebsite} + Mock -CommandName Set-FTPAuthorization + Mock -CommandName Update-WebsiteBinding + Mock -CommandName Set-SslInfo + Mock -CommandName Confirm-UniqueSslInfo { return $false } + + Set-TargetResource @MockParameters + + It 'Should call all the mocks' { + Assert-MockCalled -CommandName Set-ItemProperty -Exactly 18 + Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 2 + Assert-MockCalled -CommandName New-Webftpsite -Exactly 1 + Assert-MockCalled -CommandName Set-SslInfo -Exactly 1 + Assert-MockCalled -CommandName Set-FTPAuthorization -Exactly 1 + Assert-MockCalled -CommandName Update-WebsiteBinding -Exactly 1 + } + } + + Context 'New-Webftpsite throws an error' { + + Mock -CommandName Get-Website + Mock -CommandName Get-WebConfiguration + Mock -CommandName New-Webftpsite -MockWith {throw} + + It 'Should throw the correct error' { + $ErrorId = 'ErrorFtpSiteCreationFailure' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation + $ErrorMessage = $LocalizedData.ErrorFtpSiteCreationFailure -f $MockParameters.Name, 'ScriptHalted' + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { Set-TargetResource @MockParameters } | Should Throw $ErrorRecord + } + } + } + + Describe "how $DSCResourceName\Compare-DirectoryBrowseFlags responds" { + + Context 'Returns False when DirectoryBrowseFlags are incorrect' { + + $MockLogOutput = @{ + directoryBrowse = @{ + showFlags = 'LongDate' + } + } + + $MockWebsite = @{ + Name = 'MockFtp' + ftpServer = $MockLogOutput + } + + Mock -CommandName Get-WebSite -MockWith { return $MockWebsite } + + $result = Compare-DirectoryBrowseFlags -Site $MockWebsite.Name -DirectoryBrowseflags 'StyleUnix' + + It 'Should return False' { + $result | Should be $false + } + } + + Context 'Returns True when DirectoryBrowseFlags are correct' { + + $MockLogOutput = @{ + directoryBrowse = @{ + showFlags = 'LongDate' + } + } + + $MockWebsite = @{ + Name = 'MockFtp' + ftpServer = $MockLogOutput + } + + Mock -CommandName Get-WebSite -MockWith { return $MockWebsite } + + $result = Compare-DirectoryBrowseFlags -Site $MockWebsite.Name -DirectoryBrowseflags 'LongDate' + + It 'Should return True' { + $result | Should be $true + } + } + } + + Describe "how $DSCResourceName\Confirm-UniqueFTPAuthorization responds" { + + $MockCurrentFtpAuthorizationInfo = @( + [PSCustomObject]@{ + accessType = 'Allow' + users = 'User1' + roles = '' + permissions = 'Read' + } + [PSCustomObject]@{ + accessType = 'Deny' + users = 'User1' + roles = '' + permissions = 'Write' + } + [PSCustomObject]@{ + accessType = 'Allow' + users = '' + roles = 'Group2' + permissions = 'Read' + } + [PSCustomObject]@{ + accessType = 'Deny' + users = '' + roles = 'Group2' + permissions = 'Write' + } + ) + + $MockUserAuthorizationInfo = New-CimInstance ` + -ClassName MSFT_FTPAuthorizationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + accessType = 'Deny' + users = 'User1' + roles = '' + permissions = 'Write' + } + + $MockGroupAuthorizationInfo = New-CimInstance ` + -ClassName MSFT_FTPAuthorizationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + accessType = 'Deny' + users = '' + roles = 'Group2' + permissions = 'Write' + } + + Context 'Returns True when UniqueFTPAuthorization for a user is correct' { + + $result = Confirm-UniqueFTPAuthorization -CurrentAuthorizationCollection $MockCurrentFtpAuthorizationInfo ` + -Authorization $MockUserAuthorizationInfo ` + -Property users + + It 'Should return True' { + $result | Should be $true + } + } + + Context 'Returns True when UniqueFTPAuthorization for a group is correct' { + + $result = Confirm-UniqueFTPAuthorization -CurrentAuthorizationCollection $MockCurrentFtpAuthorizationInfo ` + -Authorization $MockGroupAuthorizationInfo ` + -Property roles + + It 'Should return True' { + $result | Should be $true + } + } + + Context 'Returns False when UniqueFTPAuthorization for a user is incorrect' { + + $contextMockUserAuthorizationInfo = $MockUserAuthorizationInfo.Clone() + $contextMockUserAuthorizationInfo.accessType = 'Allow' + + $result = Confirm-UniqueFTPAuthorization -CurrentAuthorizationCollection $MockCurrentFtpAuthorizationInfo ` + -Authorization $contextMockUserAuthorizationInfo ` + -Property users + + It 'Should return False' { + $result | Should be $false + } + } + + Context 'Returns False when UniqueFTPAuthorization for a group is incorrect' { + + $contextMockGroupAuthorizationInfo = $MockGroupAuthorizationInfo.Clone() + $contextMockGroupAuthorizationInfo.accessType = 'Allow' + + $result = Confirm-UniqueFTPAuthorization -CurrentAuthorizationCollection $MockCurrentFtpAuthorizationInfo ` + -Authorization $contextMockGroupAuthorizationInfo ` + -Property roles + + It 'Should return False' { + $result | Should be $false + } + } + } + + Describe "how $DSCResourceName\Confirm-UniqueSslInfo responds" { + + $MockSslInfo = New-CimInstance ` + -ClassName MSFT_FTPSslInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + ControlChannelPolicy = 'SslAllow' + DataChannelPolicy = 'SslAllow' + RequireSsl128 = $true + CertificateThumbprint = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234567890' + CertificateStoreName = 'My' + } + + $MockSslInfoSingle = New-CimInstance ` + -ClassName MSFT_FTPSslInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + RequireSsl128 = $true + } + + Context 'Returns False when Confirm-UniqueSslInfo is incorrect' { + + $MockFtpServerInfo = @( + @{ + security = @{ + ssl = @( + New-Object -TypeName PSObject -Property @{ + serverCertHash = '' + serverCertStoreName = '' + ssl128 = $false + controlChannelPolicy = '' + dataChannelPolicy = '' + } + ) + } + } + ) + + $MockWebsite = @{ + Name = 'MockFtp' + ftpServer = $MockFtpServerInfo + } + + Mock -CommandName Get-WebSite -MockWith { return $MockWebsite } + Mock -CommandName Test-Path {Return $true} + + $result = Confirm-UniqueSslInfo -Site $MockWebsite.Name -SslInfo $MockSslInfo + + It 'Should return False' { + $result | Should be $false + } + } + + Context 'Returns True when Confirm-UniqueSslInfo is correct' { + + $MockFtpServerInfo = @( + @{ + security = @{ + ssl = @( + New-Object -TypeName PSObject -Property @{ + controlChannelPolicy = 'SslAllow' + dataChannelPolicy = 'SslAllow' + ssl128 = $true + serverCertHash = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234567890' + serverCertStoreName = 'My' + } + ) + } + } + ) + + $MockWebsite = @{ + Name = 'MockFtp' + ftpServer = $MockFtpServerInfo + } + + Mock -CommandName Test-Path {Return $true} + Mock -CommandName Get-WebSite -MockWith { return $MockWebsite } + + $result = Confirm-UniqueSslInfo -Site $MockWebsite.Name -SslInfo $MockSslInfo + + It 'Should return True' { + $result | Should be $true + } + } + + Context 'Returns False when Confirm-UniqueSslInfo is incorrect for single property' { + + $MockFtpServerInfo = @( + @{ + security = @{ + ssl = @( + New-Object -TypeName PSObject -Property @{ + serverCertHash = '' + serverCertStoreName = '' + ssl128 = $false + controlChannelPolicy = '' + dataChannelPolicy = '' + } + ) + } + } + ) + + $MockWebsite = @{ + Name = 'MockFtp' + ftpServer = $MockFtpServerInfo + } + + Mock -CommandName Get-WebSite -MockWith { return $MockWebsite } + Mock -CommandName Test-Path {return $true} + + $result = Confirm-UniqueSslInfo -Site $MockWebsite.Name -SslInfo $MockSslInfoSingle + + It 'Should return False' { + $result | Should be $false + } + } + + Context 'Returns True when Confirm-UniqueSslInfo is correct for single property' { + + $MockFtpServerInfo = @( + @{ + security = @{ + ssl = @( + New-Object -TypeName PSObject -Property @{ + controlChannelPolicy = 'SslAllow' + dataChannelPolicy = 'SslAllow' + ssl128 = $true + serverCertHash = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234567890' + serverCertStoreName = 'My' + } + ) + } + } + ) + + $MockWebsite = @{ + Name = 'MockFtp' + ftpServer = $MockFtpServerInfo + } + + Mock -CommandName Test-Path {return $true} + Mock -CommandName Get-WebSite -MockWith { return $MockWebsite } + + $result = Confirm-UniqueSslInfo -Site $MockWebsite.Name -SslInfo $MockSslInfoSingle + + It 'Should return True' { + $result | Should be $true + } + } + + Context 'Throws when cert does not exist' { + + Mock -CommandName Test-Path {Return $false} + + Mock -CommandName Get-WebSite -MockWith { return $MockWebsite } + + It 'Should throw the correct error' { + $ErrorId = 'ErrorServerCertHashFailure' + $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult + $ErrorMessage = $LocalizedData.ErrorServerCertHashFailure -f $MockSslInfo.CertificateThumbprint, $MockSslInfo.CertificateStoreName + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + + { Confirm-UniqueSslInfo -Site 'Name' -SslInfo $MockSslInfo } | Should Throw $ErrorRecord + } + } + } + + Describe "how $DSCResourceName\Get-SslInfo responds" { + + $MockFtpServerInfo = @{ + ftpServer = @{ + security = @{ + ssl = ( + New-Object -TypeName PSObject -Property @{ + controlChannelPolicy = 'SslAllow' + dataChannelPolicy = 'SslAllow' + ssl128 = $true + serverCertHash = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234567890' + serverCertStoreName = 'My' + } + ) + } + } + } + + $MockFtpSite = 'MockFtp' + + Context 'Expected behavior' { + + Mock -CommandName Get-Item -MockWith { return $null } + + It 'Should not throw an error' { + { Get-SslInfo -Site $MockFtpSite }| + Should Not Throw + } + + It 'Should call Get-Item 5 times' { + Assert-MockCalled -CommandName Get-Item -Exactly 5 + } + } + + Context 'Returns empty values' { + + Mock -CommandName Get-Item -MockWith { return $null } + + $result = Get-SslInfo -Site $MockFtpSite + + It 'Should contain 5 properties' { + $result.CimInstanceProperties.Count | Should Be 5 + } + + It 'Should have all the properties set to empty value' { + $result.ControlChannelPolicy | Should BeNullOrEmpty + $result.DataChannelPolicy | Should BeNullOrEmpty + $result.RequireSsl128 | Should Be $false + $result.CertificateThumbprint | Should BeNullOrEmpty + $result.CertificateStoreName | Should BeNullOrEmpty + } + + It 'Should call Get-Item 5 times' { + Assert-MockCalled -CommandName Get-Item -Exactly 5 + } + } + + Context 'Returns proper values' { + + Mock -CommandName Get-Item -MockWith { return $MockFtpServerInfo } + + $result = Get-SslInfo -Site $MockFtpSite + + It 'Should contain 5 properties' { + $result.CimInstanceProperties.Count | Should Be 5 + } + + It 'Should have all the properties set' { + $result.ControlChannelPolicy | Should Be 'SslAllow' + $result.DataChannelPolicy | Should Be 'SslAllow' + $result.RequireSsl128 | Should Be $true + $result.CertificateThumbprint | Should Be 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234567890' + $result.CertificateStoreName | Should Be 'My' + } + + It 'Should call Get-Item 5 times' { + Assert-MockCalled -CommandName Get-Item -Exactly 5 + } + } + } + + Describe "how $DSCResourceName\Get-AuthorizationInfo responds" { + + $MockFtpServerInfo = @{ + Collection = @( + New-Object -TypeName PSObject -Property @{ + accessType = 'Allow' + users = 'MockUser1,MockUser2' + roles = 'MockGroup' + permissions = 'Read,Write' + } + New-Object -TypeName PSObject -Property @{ + accessType = 'Deny' + users = 'MockUser3' + roles = 'MockGroup2' + permissions = 'Write' + } + ) + } + + $MockFtpSite = 'MockFtp' + + Context 'Expected behavior' { + + Mock -CommandName Get-WebConfiguration -MockWith { return $null } + + It 'Should not throw an error' { + { Get-AuthorizationInfo -Site $MockFtpSite }| + Should Not Throw + } + + It 'Should call Get-WebConfiguration 4 times' { + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + } + + It 'Should return nothing' { + Get-AuthorizationInfo -Site $MockFtpSite | Should BeNullOrEmpty + } + } + + Context 'Returns proper values' { + + Mock -CommandName Get-WebConfiguration -MockWith { return $MockFtpServerInfo } + + $result = Get-AuthorizationInfo -Site $MockFtpSite + + It 'Should contain 4 properties' { + $result[0].CimInstanceProperties.Count | Should Be 4 + $result[1].CimInstanceProperties.Count | Should Be 4 + } + + It 'Should have all the properties set' { + $result[0].accessType | Should Be 'Allow' + $result[0].users | Should Be 'MockUser1,MockUser2' + $result[0].roles | Should Be 'MockGroup' + $result[0].permissions | Should Be 'Read,Write' + + $result[1].accessType | Should Be 'Deny' + $result[1].users | Should Be 'MockUser3' + $result[1].roles | Should Be 'MockGroup2' + $result[1].permissions | Should Be 'Write' + } + + It 'Should call Get-WebConfiguration once' { + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + } + } + } + + Describe "how $DSCResourceName\Set-SslInfo responds" { + + $MockSslInfo = New-CimInstance ` + -ClassName MSFT_FTPSslInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + ControlChannelPolicy = 'SslAllow' + DataChannelPolicy = 'SslAllow' + RequireSsl128 = $true + CertificateThumbprint = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234567890' + CertificateStoreName = 'My' + } + + $MockSslInfoSingle = New-CimInstance ` + -ClassName MSFT_FTPSslInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + RequireSsl128 = '' + } + + $MockFtpSite = 'MockFtp' + + Mock -CommandName Set-ItemProperty + + Context "Expected behavior" { + + It 'Should not throw an error' { + { Set-SslInfo -Site $MockFtpSite -SslInfo $MockSslInfo }| + Should Not Throw + } + + It 'Should call Set-ItemProperty 5 times' { + Assert-MockCalled -CommandName Set-ItemProperty -Exactly 5 + } + } + + Context "Update single property" { + + Set-SslInfo -Site $MockFtpSite -SslInfo $MockSslInfoSingle + + It 'Should call Set-ItemProperty once' { + Assert-MockCalled -CommandName Set-ItemProperty -Exactly 1 + } + } + } + + Describe "how $DSCResourceName\Set-FTPAuthorization responds" { + + $MockFtpSiteName = 'FTP' + + $MockAuthorizationInfo = @( + New-CimInstance -ClassName MSFT_FTPAuthorizationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + accessType = 'Allow' + users = 'User1' + roles = 'Group1' + permissions = 'Read' + } + New-CimInstance -ClassName MSFT_FTPAuthorizationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + accessType = 'Deny' + users = 'User2' + roles = 'Group2' + permissions = 'Write' + } + ) + + Context "Expected behavior" { + + Mock -CommandName Clear-WebConfiguration + Mock -CommandName Add-WebConfiguration + + It 'Should not throw an error' { + { Set-FTPAuthorization -Site $MockFtpSiteName -AuthorizationInfo $MockAuthorizationInfo }| + Should Not Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled -CommandName Clear-WebConfiguration -Exactly 1 + Assert-MockCalled -CommandName Add-WebConfiguration -Exactly 2 + } + } + } + + Describe "how $DSCResourceName\Test-AuthorizationInfo responds" { + + $MockFtpSiteName = 'FTP' + + $MockAuthorizationInfo = @( + New-CimInstance -ClassName MSFT_FTPAuthorizationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + accessType = 'Allow' + users = 'User1' + roles = '' + permissions = 'Read' + } + New-CimInstance -ClassName MSFT_FTPAuthorizationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + accessType = 'Deny' + users = 'User1' + roles = '' + permissions = 'Write' + } + New-CimInstance -ClassName MSFT_FTPAuthorizationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + accessType = 'Allow' + users = '' + roles = 'Group2' + permissions = 'Read' + } + New-CimInstance -ClassName MSFT_FTPAuthorizationInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + accessType = 'Deny' + users = '' + roles = 'Group2' + permissions = 'Write' + } + ) + + $MockFtpAuthorizationOutput = @{ + Collection = @( + [PSCustomObject]@{ + accessType = 'Deny' + users = 'User1' + roles = '' + permissions = 'Write' + } + [PSCustomObject]@{ + accessType = 'Allow' + users = 'User1' + roles = '' + permissions = 'Read' + } + [PSCustomObject]@{ + accessType = 'Deny' + users = '' + roles = 'Group2' + permissions = 'Write' + } + [PSCustomObject]@{ + accessType = 'Allow' + users = '' + roles = 'Group2' + permissions = 'Read' + } + ) + } + + Mock -CommandName Get-WebConfiguration -MockWith { return $MockFtpAuthorizationOutput } + + Context "Expected behavior" { + + It 'Should not throw an error' { + { Test-AuthorizationInfo -Site $MockFtpSiteName ` + -AuthorizationInfo $MockAuthorizationInfo}| + Should Not Throw + } + + It 'Should call Get-WebConfiguration once' { + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + } + } + + Context 'Returns True when AuthorizationInfo is identical' { + + $result = Test-AuthorizationInfo -Site $MockFtpSiteName ` + -AuthorizationInfo $MockAuthorizationInfo + + It 'Should return True' { + $result | Should be $true + } + + It 'Should call Get-WebConfiguration once' { + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + } + } + + Context 'Returns False when AuthorizationInfo is different in count' { + + $contextMockAuthorizationInfo = @() + $contextMockAuthorizationInfo += $MockAuthorizationInfo[0].Clone() + + Mock -CommandName Confirm-UniqueFTPAuthorization + + $result = Test-AuthorizationInfo -Site $MockFtpSiteName -AuthorizationInfo $contextMockAuthorizationInfo + + It 'Should return False' { + $result | Should be $false + } + + It 'Should call expected mocks' { + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + Assert-MockCalled -CommandName Confirm-UniqueFTPAuthorization -Exactly 0 + } + } + + Context 'Returns False when AuthorizationInfo is different' { + + $contextMockAuthorizationInfo = @() + $contextMockAuthorizationInfo += $MockAuthorizationInfo[0].Clone() + $contextMockAuthorizationInfo += $MockAuthorizationInfo[1].Clone() + $contextMockAuthorizationInfo += $MockAuthorizationInfo[2].Clone() + $contextMockAuthorizationInfo += $MockAuthorizationInfo[3].Clone() + $contextMockAuthorizationInfo[2].permissions = 'Read,Write' + + $result = Test-AuthorizationInfo -Site $MockFtpSiteName -AuthorizationInfo $contextMockAuthorizationInfo + + It 'Should return False' { + $result | Should be $false + } + + It 'Should call Get-WebConfiguration once' { + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + } + } + } + } + #endregion +} +finally +{ + #region FOOTER + Restore-TestEnvironment -TestEnvironment $TestEnvironment + #endregion +} diff --git a/Tests/Unit/MSFT_xWebApplication.Tests.ps1 b/Tests/Unit/MSFT_xWebApplication.Tests.ps1 index 87f9227cb..c58406f23 100644 --- a/Tests/Unit/MSFT_xWebApplication.Tests.ps1 +++ b/Tests/Unit/MSFT_xWebApplication.Tests.ps1 @@ -1,32 +1,34 @@ -$script:DSCModuleName = 'xWebAdministration' -$script:DSCResourceName = 'MSFT_xWebApplication' +$script:DSCModuleName = 'xWebAdministration' +$script:DSCResourceName = 'MSFT_xWebApplication' +$script:DSCHelperModuleName = 'Helper' #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'))) ) +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 (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\MockWebAdministrationWindowsFeature.psm1') - -$TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit +$TestEnvironment = Initialize-TestEnvironment -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit #endregion try { + #region Pester Tests InModuleScope -ModuleName $script:DSCResourceName -ScriptBlock { - $script:DSCResourceName = 'MSFT_xWebApplication' + $script:DSCResourceName = 'MSFT_xWebApplication' + $script:DSCHelperModuleName = 'Helper' - $MockAuthenticationInfo = New-CimInstance -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly ` - -Property @{Anonymous=$true;Basic=$false;Digest=$false;Windows=$true} + $MockAuthenticationInfo = New-CimInstance ` + -ClassName MSFT_xWebApplicationAuthenticationInformation ` + -ClientOnly ` + -Property @{Anonymous=$true;Basic=$false;Digest=$false;Windows=$true} ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' $MockParameters = @{ Website = 'MockSite' @@ -45,6 +47,7 @@ try $MockWebApplicationOutput = @{ Website = 'MockSite' Name = 'MockApp' + ItemXPath = ("/system.applicationHost/sites/site[@name='{0}']/application[@path='/{1}']" -f $MockParameters.Website, $MockParameters.Name) applicationPool = 'MockPool' PhysicalPath = 'C:\MockSite\MockApp' SslFlags = 'Ssl' @@ -54,56 +57,36 @@ try ApplicationType = 'MockApplicationType' AuthenticationInfo = $MockAuthenticationInfo EnabledProtocols = 'http' - Count = '1' + Count = 1 } $GetWebConfigurationOutput = @( - @{ - SectionPath = 'MockSectionPath' - PSPath = 'MockPSPath' - SslFlags = 'Ssl' - Collection = @( - [PSCustomObject]@{Name = 'MockServiceAutoStartProvider' ;Type = 'MockApplicationType'} - ) - } - ) - - Describe "$script:DSCResourceName\Assert-Module" { - - Context 'WebAdminstration module is not installed' { - - Mock -ModuleName Helper -CommandName Get-Module -MockWith { - return $null - } - - It 'should throw an error' { - { Assert-Module } | - Should Throw - - } - + @{ + SectionPath = 'MockSectionPath' + PSPath = 'MockPSPath' + SslFlags = 'Ssl' + Collection = @( + [PSCustomObject]@{Name = 'MockServiceAutoStartProvider' ;Type = 'MockApplicationType'} + ) } + ) - } - - Describe "$script:DSCResourceName\Get-TargetResource" { + Describe "$DSCResourceName\Get-TargetResource" { $MockParameters = @{ - Website = 'MockSite' - Name = 'MockApp' - WebAppPool = 'MockPool' - PhysicalPath = 'C:\MockSite\MockApp' + Website = 'MockSite' + Name = 'MockApp' + WebAppPool = 'MockPool' + PhysicalPath = 'C:\MockSite\MockApp' } Mock -CommandName Get-WebConfiguration -MockWith { return $GetWebConfigurationOutput } - Mock Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Anonymous') } - - Mock Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Windows') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfigurationProperty ` + -MockWith {} Mock -CommandName Assert-Module -MockWith {} @@ -113,15 +96,10 @@ try return $null } - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } - - It 'should return Absent' { + It 'Should return Absent' { $Result = Get-TargetResource @MockParameters $Result.Ensure | Should Be 'Absent' } - } Context 'Present should return correctly' { @@ -130,94 +108,56 @@ try return $MockWebApplicationOutput } - Mock -CommandName Get-WebConfiguration -MockWith { - return $GetWebConfigurationOutput - } - - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $GetAuthenticationInfo - } - - Mock Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Anonymous') } - - Mock Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Windows') } - - It 'should return Present' { + It 'Should return Present' { $Result = Get-TargetResource @MockParameters $Result.Ensure | Should Be 'Present' } - } - } - Describe "how $script:DSCResourceName\Test-TargetResource responds to Ensure = 'Absent'" { - - Mock -CommandName Get-SslFlags -MockWith { - return $GetSslFlags - } - - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $GetAuthenticationInfo - } + Describe "how $DSCResourceName\Test-TargetResource responds to Ensure = 'Absent'" { Mock -CommandName Assert-Module -MockWith {} Context 'Web Application does not exist' { + Mock -CommandName Get-WebApplication -MockWith { return $null } - It 'should return True' { + It 'Should return True' { $Result = Test-TargetResource -Ensure 'Absent' @MockParameters $Result | Should Be $true } - } Context 'Web Application exists' { + Mock -CommandName Get-WebApplication -MockWith { return @{Count = 1} } - It 'should return False' { + It 'Should return False' { $Result = Test-TargetResource -Ensure 'Absent' @MockParameters $Result | Should Be $false } - } - } - Describe "how $script:DSCResourceName\Test-TargetResource responds to Ensure = 'Present'" { + Describe "how $DSCResourceName\Test-TargetResource responds to Ensure = 'Present'" { Mock -CommandName Assert-Module -MockWith {} Context 'Web Application does not exist' { - $MockAuthenticationInfo = New-CimInstance -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly ` - -Property @{Anonymous=$true;Basic=$false;Digest=$false;Windows=$false} - Mock -CommandName Get-WebApplication -MockWith { return $null } - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } - - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } - - It 'should return False' { + It 'Should return False' { $Result = Test-TargetResource -Ensure 'Present' @MockParameters $Result | Should Be $false } - } Context 'Web Application exists and is in the desired state' { @@ -226,320 +166,329 @@ try return $MockWebApplicationOutput } - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } - - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } - - Mock Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Anonymous') } - - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } - Mock Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Windows') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } - It 'should return True' { + It 'Should return True' { $Result = Test-TargetResource -Ensure 'Present' @MockParameters $Result | Should Be $true } - } Context 'Web Application exists but has a different WebAppPool' { - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } + $contextMockWebApplicationOutput = $MockWebApplicationOutput.Clone() + $contextMockWebApplicationOutput.applicationPool = 'MockPoolOther' - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = 'MockPoolOther' - PhysicalPath = $MockWebApplicationOutput.PhysicalPath - PreloadEnabled = $MockWebApplicationOutput.PreloadEnabled - ServiceAutoStartEnabled = $MockWebApplicationOutput.ServiceAutoStartEnabled - ServiceAutoStartProvider = $MockWebApplicationOutput.ServiceAutoStartProvider - ApplicationType = $MockWebApplicationOutput.ApplicationType - EnabledProtocols = $MockWebApplicationOutput.EnabledProtocols - Count = 1 - } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } + Mock -CommandName Get-WebApplication -MockWith { + return $contextMockWebApplicationOutput } - It 'should return False' { + It 'Should return False' { $Result = Test-TargetResource -Ensure 'Present' @MockParameters $Result | Should Be $False } - } Context 'Web Application exists but has a different PhysicalPath' { - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } + $contextMockWebApplicationOutput = $MockWebApplicationOutput.Clone() + $contextMockWebApplicationOutput.PhysicalPath = 'C:\MockSite\MockAppOther' - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockWebApplicationOutput.applicationPool - PhysicalPath = 'C:\MockSite\MockAppOther' - PreloadEnabled = $MockWebApplicationOutput.PreloadEnabled - ServiceAutoStartProvider = $MockWebApplicationOutput.ServiceAutoStartProvider - ServiceAutoStartEnabled = $MockWebApplicationOutput.ServiceAutoStartEnabled - EnabledProtocols = $MockWebApplicationOutput.EnabledProtocols - Count = 1 - } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } + + Mock -CommandName Get-WebApplication -MockWith { + return $contextMockWebApplicationOutput } - It 'should return False' { + It 'Should return False' { $Result = Test-TargetResource -Ensure 'Present' @MockParameters $Result | Should Be $False } - } Context 'Check SslFlags is different' { - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } + $contextGetWebConfigurationOutput = @() + $contextGetWebConfigurationOutput += $GetWebConfigurationOutput[0].Clone() + $contextGetWebConfigurationOutput[0].SslFlags = 'MockSsl' - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { $contextGetWebConfigurationOutput } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockWebApplicationOutput.applicationPool - PhysicalPath = $MockWebApplicationOutput.PhysicalPath - PreloadEnabled = 'false' - ServiceAutoStartProvider = $MockWebApplicationOutput.ServiceAutoStartProvider - ServiceAutoStartEnabled = $MockWebApplicationOutput.ServiceAutoStartEnabled - EnabledProtocols = $MockWebApplicationOutput.EnabledProtocols - Count = 1 - } - } + return $MockWebApplicationOutput + } $Result = Test-TargetResource -Ensure 'Present' @MockParameters - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } - } Context 'Check AuthenticationInfo is different' { + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -eq 'Anonymous') } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest', 'Windows')) } + Mock -CommandName Get-WebApplication -MockWith { return $MockWebApplicationOutput } - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } + $Result = Test-TargetResource -Ensure 'Present' @MockParameters - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + It 'Should return False' { + $Result | Should Be $false + } - Mock Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Anonymous') } + It 'Should call all the mocks' { + Assert-MockCalled ` + -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -Exactly 4 + } + } - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } + Context 'Check AuthenticationInfo is different from default' { - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock Test-AuthenticationEnabled { return $false } ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` -ParameterFilter { ($Type -eq 'Windows') } - $MockAuthenticationInfo = New-CimInstance -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly ` - -Property @{Anonymous=$true;Basic=$false;Digest=$false;Windows=$true} + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Basic', 'Digest')) } - $Result = Test-TargetResource -Ensure 'Present' @MockParameters + Mock -CommandName Get-WebApplication -MockWith { + return $MockWebApplicationOutput + } + + $contextMockParameters = $MockParameters.Clone() + $contextMockParameters.Remove('AuthenticationInfo') - It 'should return False' { + $Result = Test-TargetResource -Ensure 'Present' @contextMockParameters + + It 'Should return False' { $Result | Should Be $false } + It 'Should call all the mocks' { + Assert-MockCalled ` + -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -Exactly 4 + } } Context 'Check Preload is different' { - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } + $contextMockWebApplicationOutput = $MockWebApplicationOutput.Clone() + $contextMockWebApplicationOutput.PreloadEnabled = $false - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockWebApplicationOutput.applicationPool - PhysicalPath = $MockWebApplicationOutput.PhysicalPath - PreloadEnabled = 'false' - ServiceAutoStartEnabled = $MockWebApplicationOutput.ServiceAutoStartEnabled - ServiceAutoStartProvider = $MockWebApplicationOutput.ServiceAutoStartProvider - ApplicationType = $MockWebApplicationOutput.ApplicationType - EnabledProtocols = $MockWebApplicationOutput.EnabledProtocols - Count = 1 - } - } + return $contextMockWebApplicationOutput + } $Result = Test-TargetResource -Ensure 'Present' @MockParameters - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } - } Context 'Check ServiceAutoStartEnabled is different' { - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } + $contextMockWebApplicationOutput = $MockWebApplicationOutput.Clone() + $contextMockWebApplicationOutput.ServiceAutoStartEnabled = $false - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockWebApplicationOutput.applicationPool - PhysicalPath = $MockWebApplicationOutput.PhysicalPath - PreloadEnabled = $MockWebApplicationOutput.PreloadEnabled - ServiceAutoStartEnabled = 'false' - ServiceAutoStartProvider = $MockWebApplicationOutput.ServiceAutoStartProvider - ApplicationType = $MockWebApplicationOutput.ApplicationType - EnabledProtocols = $MockWebApplicationOutput.EnabledProtocols - Count = 1 - } - } + return $contextMockWebApplicationOutput + } $Result = Test-TargetResource -Ensure 'Present' @MockParameters - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } - } Context 'Check ServiceAutoStartProvider is different' { - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } + $contextMockWebApplicationOutput = $MockWebApplicationOutput.Clone() + $contextMockWebApplicationOutput.ServiceAutoStartProvider = 'MockOtherServiceAutoStartProvider' + $contextMockWebApplicationOutput.ApplicationType = 'MockOtherApplicationType' - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq '/system.applicationHost/serviceAutoStartProviders'} -MockWith { - return $null - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfiguration ` + -ParameterFilter { $filter -eq '/system.applicationHost/serviceAutoStartProviders' }` + -MockWith { return $null } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockWebApplicationOutput.applicationPool - PhysicalPath = $MockWebApplicationOutput.PhysicalPath - PreloadEnabled = $MockWebApplicationOutput.PreloadEnabled - ServiceAutoStartEnabled = $MockWebApplicationOutput.ServiceAutoStartEnabled - ServiceAutoStartProvider = 'ServiceAutoStartProviderOther' - ApplicationType = 'ApplicationTypeOther' - Count = 1 - } - } + return $contextMockWebApplicationOutput + } $Result = Test-TargetResource -Ensure 'Present' @MockParameters - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } - } Context 'Check EnabledProtocols is different' { - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } + $contextMockWebApplicationOutput = $MockWebApplicationOutput.Clone() + $contextMockWebApplicationOutput.EnabledProtocols = 'https' - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq '/system.applicationHost/serviceAutoStartProviders'} -MockWith { - return $null - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockWebApplicationOutput.applicationPool - PhysicalPath = $MockWebApplicationOutput.PhysicalPath - PreloadEnabled = $MockWebApplicationOutput.PreloadEnabled - ServiceAutoStartEnabled = $MockWebApplicationOutput.ServiceAutoStartEnabled - ServiceAutoStartProvider = $MockWebApplicationOutput.ServiceAutoStartProvider - ApplicationType = $MockWebApplicationOutput.ApplicationType - EnabledProtocols = 'http' - Count = 1 - } - } + return $contextMockWebApplicationOutput + } $Result = Test-TargetResource -Ensure 'Present' @MockParameters - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } - } - } - Describe "how $script:DSCResourceName\Set-TargetResource responds to Ensure = 'Absent'" { - - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } - - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Describe "how $DSCResourceName\Set-TargetResource responds to Ensure = 'Absent'" { Mock -CommandName Assert-Module -MockWith {} Context 'Web Application exists' { + Mock -CommandName Remove-WebApplication - It 'should call expected mocks' { + It 'Should call expected mocks' { Set-TargetResource -Ensure 'Absent' @MockParameters Assert-MockCalled -CommandName Remove-WebApplication -Exactly 1 } - } - } - Describe "how $script:DSCResourceName\Set-TargetResource responds to Ensure = 'Present'" { + Describe "how $DSCResourceName\Set-TargetResource responds to Ensure = 'Present'" { Mock -CommandName Assert-Module -MockWith {} @@ -550,14 +499,13 @@ try $script:mockGetWebApplicationCalled++ if($script:mockGetWebApplicationCalled -eq 1) { - return $null + return $null } else { return @{ ApplicationPool = $MockParameters.WebAppPool PhysicalPath = $MockParameters.PhysicalPath - ItemXPath = $MockItemXPath Count = 1 } } @@ -565,291 +513,250 @@ try Mock -CommandName Get-WebApplication -MockWith $mockWebApplication - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } - - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq '/system.applicationHost/serviceAutoStartProviders'} -MockWith { - return $null - } - - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } - - Mock Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Anonymous') } - - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $null } - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfiguration ` + -ParameterFilter { $filter -eq '/system.applicationHost/serviceAutoStartProviders' } ` + -MockWith { return $null } - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Windows') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfigurationProperty ` + -MockWith { return @{ Value = $false } } - Mock Test-SslFlags { return $null } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfigurationProperty ` + -ParameterFilter { $Filter -match 'Anonymous'} ` + -MockWith { return @{ Value = $true } } Mock -CommandName Add-WebConfiguration Mock -CommandName New-WebApplication Mock -CommandName Set-WebConfigurationProperty Mock -CommandName Set-ItemProperty - Mock -CommandName Set-Authentication - - It 'should call expected mocks' { + Mock -ModuleName $DSCHelperModuleName -CommandName Set-Authentication + It 'Should call expected mocks' { Set-TargetResource -Ensure 'Present' @MockParameters + Assert-MockCalled -CommandName Get-WebApplication -Exactly 2 Assert-MockCalled -CommandName New-WebApplication -Exactly 1 + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Get-WebConfiguration -Exactly 1 Assert-MockCalled -CommandName Set-ItemProperty -Exactly 4 Assert-MockCalled -CommandName Add-WebConfiguration -Exactly 1 Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 1 - Assert-MockCalled -CommandName Test-AuthenticationEnabled -Exactly 4 - Assert-MockCalled -CommandName Set-Authentication -Exactly 4 - + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Get-WebConfigurationProperty -Exactly 4 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Set-Authentication -Exactly 4 } - } Context 'Web Application exists but has a different WebAppPool' { - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + $contextMockWebApplicationOutput = $MockWebApplicationOutput.Clone() + $contextMockWebApplicationOutput.ApplicationPool = 'MockPoolOther' Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = 'MockPoolOther' - PhysicalPath = $MockWebApplicationOutput.PhysicalPath - ItemXPath = ("/system.applicationHost/sites/site[@name='{0}']/application[@path='/{1}']" -f $MockWebApplicationOutput.Website, $MockWebApplicationOutput.Name) - PreloadEnabled = $MockWebApplicationOutput.PreloadEnabled - ServiceAutoStartEnabled = $MockWebApplicationOutput.ServiceAutoStartEnabled - ServiceAutoStartProvider = $MockWebApplicationOutput.ServiceAutoStartProvider - ApplicationType = $MockWebApplicationOutput.ApplicationType - EnabledProtocols = $MockWebApplicationOutput.EnabledProtocols - Count = 1 - } - - } - - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } - - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo + return $contextMockWebApplicationOutput } - Mock Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Anonymous') } - - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } - Mock Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Windows') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } - Mock -CommandName Add-WebConfiguration - Mock -CommandName New-WebApplication Mock -CommandName Set-WebConfigurationProperty - Mock -CommandName Set-WebConfiguration - Mock -CommandName Set-ItemProperty - It 'should call expected mocks' { + It 'Should call expected mocks' { Set-TargetResource -Ensure 'Present' @MockParameters Assert-MockCalled -CommandName Get-WebApplication -Exactly 1 - Assert-MockCalled -CommandName Set-WebConfigurationProperty -Scope It -Exactly 1 ` - -ParameterFilter { ` - ($Filter -eq "/system.applicationHost/sites/site[@name='MockSite']/application[@path='/MockApp']") -And ` - ($Name -eq 'applicationPool') -And ` - ($Value -eq 'MockPool') ` - } + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Test-AuthenticationEnabled -Exactly 4 + Assert-MockCalled ` + -CommandName Set-WebConfigurationProperty ` + -Scope It ` + -Exactly 1 ` + -ParameterFilter { + ($Filter -eq "/system.applicationHost/sites/site[@name='MockSite']/application[@path='/MockApp']") -And ` + ($Name -eq 'applicationPool') -And ` + ($Value -eq 'MockPool') ` + } } - } Context 'Web Application exists but has a different PhysicalPath' { - Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockWebApplicationOutput.applicationPool - PhysicalPath = 'C:\MockSite\MockAppOther' - ItemXPath = ("/system.applicationHost/sites/site[@name='{0}']/application[@path='/{1}']" -f $MockWebApplicationOutput.Website, $MockWebApplicationOutput.Name) - PreloadEnabled = $MockWebApplicationOutput.PreloadEnabled - ServiceAutoStartEnabled = $MockWebApplicationOutput.ServiceAutoStartEnabled - ServiceAutoStartProvider = $MockWebApplicationOutput.ServiceAutoStartProvider - ApplicationType = $MockWebApplicationOutput.ApplicationType - EnabledProtocols = $MockWebApplicationOutput.EnabledProtocols - Count = 1 - } - - } - - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } + $contextMockWebApplicationOutput = $MockWebApplicationOutput.Clone() + $contextMockWebApplicationOutput.PhysicalPath = 'C:\MockSite\MockAppOther' - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo + Mock -CommandName Get-WebApplication -MockWith { + return $contextMockWebApplicationOutput } - Mock Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Anonymous') } - - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } - Mock Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Windows') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } - Mock -CommandName Add-WebConfiguration - Mock -CommandName New-WebApplication Mock -CommandName Set-WebConfigurationProperty - Mock -CommandName Set-WebConfiguration - Mock -CommandName Set-ItemProperty - - It 'should call expected mocks' { + It 'Should call expected mocks' { Set-TargetResource -Ensure 'Present' @MockParameters Assert-MockCalled -CommandName Get-WebApplication -Exactly 1 + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Test-AuthenticationEnabled -Exactly 4 } - } Context 'Web Application exists but has different AuthenticationInfo' { Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockWebApplicationOutput.applicationPool - PhysicalPath = $MockWebApplicationOutput.PhysicalPath - ItemXPath = ("/system.applicationHost/sites/site[@name='{0}']/application[@path='/{1}']" -f $MockWebApplicationOutput.Website, $MockWebApplicationOutput.Name) - PreloadEnabled = $MockWebApplicationOutput.PreloadEnabled - ServiceAutoStartEnabled = $MockWebApplicationOutput.ServiceAutoStartEnabled - ServiceAutoStartProvider = $MockWebApplicationOutput.ServiceAutoStartProvider - ApplicationType = $MockWebApplicationOutput.ApplicationType - EnabledProtocols = $MockWebApplicationOutput.EnabledProtocols - Count = 1 - } + return $MockWebApplicationOutput } - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock -CommandName Test-AuthenticationEnabled { return $true } ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` -ParameterFilter { ($Type -eq 'Anonymous') } - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest', 'Windows')) } - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } - - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Windows') } - - Mock -CommandName Set-WebConfiguration - Mock -CommandName Set-Authentication - - $MockAuthenticationInfo = New-CimInstance -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly ` - -Property @{Anonymous=$true;Basic=$false;Digest=$false;Windows=$true} - - It 'should call expected mocks' { + Mock -ModuleName $DSCHelperModuleName -CommandName Set-WebConfigurationProperty + It 'Should call expected mocks' { Set-TargetResource -Ensure 'Present' @MockParameters Assert-MockCalled -CommandName Get-WebApplication -Exactly 1 - Assert-MockCalled -CommandName Test-AuthenticationEnabled -Exactly 4 - Assert-MockCalled -CommandName Set-Authentication -Exactly 4 + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Test-AuthenticationEnabled -Exactly 4 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Set-WebConfigurationProperty -Exactly 4 } - } - Context 'Web Application exists but has different SslFlags' { + Context 'Web Application exists but has different AuthenticationInfo from default' { Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockParameters.WebAppPool - PhysicalPath = $MockParameters.PhysicalPath - ItemXPath = $MockItemXPath - PreloadEnabled = $MockParameters.PreloadEnabled - ServiceAutoStartEnabled = $MockParameters.ServiceAutoStartEnabled - ServiceAutoStartProvider = $MockParameters.ServiceAutoStartProvider - ApplicationType = $MockParameters.ApplicationType - Count = 1 - } - + return $MockWebApplicationOutput } - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return @{ - SectionPath = 'MockSectionPath' - PSPath = 'MockPSPath' - SslFlags = 'None' - Collection = @( - [PSCustomObject]@{Name = 'MockServiceAutoStartProvider' ;Type = 'MockApplicationType'} - ) - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -eq 'Windows') } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Basic', 'Digest')) } + + Mock -ModuleName $DSCHelperModuleName -CommandName Set-WebConfigurationProperty + + It 'Should call expected mocks' { + $contextMockParameters = $MockParameters.Clone() + $contextMockParameters.Remove('AuthenticationInfo') + + Set-TargetResource -Ensure 'Present' @contextMockParameters + + Assert-MockCalled -CommandName Get-WebApplication -Exactly 1 + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Test-AuthenticationEnabled -Exactly 4 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Set-WebConfigurationProperty -Exactly 4 } + } + + Context 'Web Application exists but has different SslFlags' { - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo + $contextGetWebConfigurationOutput = @() + $contextGetWebConfigurationOutput += $GetWebConfigurationOutput[0].Clone() + $contextGetWebConfigurationOutput[0].SslFlags = 'MockSsl' + + Mock -CommandName Get-WebApplication -MockWith { + return $MockWebApplicationOutput } - Mock -CommandName Add-WebConfiguration - Mock -CommandName New-WebApplication + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $contextGetWebConfigurationOutput } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } + Mock -CommandName Set-WebConfigurationProperty - Mock -CommandName Set-WebConfiguration - Mock -CommandName Set-ItemProperty It 'Should call expected mocks' { Set-TargetResource -Ensure 'Present' @MockParameters Assert-MockCalled -CommandName Get-WebApplication -Exactly 1 - Assert-MockCalled -CommandName Set-WebConfigurationProperty ` + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Test-AuthenticationEnabled -Exactly 4 + Assert-MockCalled ` + -CommandName Set-WebConfigurationProperty ` -ParameterFilter { $Name -eq 'sslFlags' } ` -Exactly 1 } } Context 'Web Application exists but has different and multiple SslFlags' { + Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockParameters.WebAppPool - PhysicalPath = $MockParameters.PhysicalPath - ItemXPath = $MockItemXPath - PreloadEnabled = $MockParameters.PreloadEnabled - ServiceAutoStartEnabled = $MockParameters.ServiceAutoStartEnabled - ServiceAutoStartProvider = $MockParameters.ServiceAutoStartProvider - ApplicationType = $MockParameters.ApplicationType - Count = 1 - } + return $MockWebApplicationOutput } Mock -CommandName Get-WebConfiguration ` -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` -MockWith { return $GetWebConfigurationOutput } - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } - Mock -CommandName Add-WebConfiguration - Mock -CommandName New-WebApplication Mock -CommandName Set-WebConfigurationProperty - Mock -CommandName Set-WebConfiguration - Mock -CommandName Set-ItemProperty It 'Should call expected mocks' { $contextParameters = $MockParameters.Clone() @@ -858,7 +765,10 @@ try Set-TargetResource -Ensure 'Present' @contextParameters Assert-MockCalled -CommandName Get-WebApplication -Exactly 1 - Assert-MockCalled -CommandName Set-WebConfigurationProperty ` + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Test-AuthenticationEnabled -Exactly 4 + Assert-MockCalled ` + -CommandName Set-WebConfigurationProperty ` -ParameterFilter { $Value -eq 'Ssl,Ssl128' -and $Name -eq 'sslFlags' } ` -Exactly 1 } @@ -866,205 +776,162 @@ try Context 'Web Application exists but has Preload not set' { - Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockWebApplicationOutput.applicationPool - PhysicalPath = $MockWebApplicationOutput.PhysicalPath - ItemXPath = ("/system.applicationHost/sites/site[@name='{0}']/application[@path='/{1}']" -f $MockWebApplicationOutput.Website, $MockWebApplicationOutput.Name) - PreloadEnabled = 'false' - ServiceAutoStartEnabled = $MockWebApplicationOutput.ServiceAutoStartEnabled - ServiceAutoStartProvider = $MockWebApplicationOutput.ServiceAutoStartProvider - ApplicationType = $MockWebApplicationOutput.ApplicationType - EnabledProtocols = $MockWebApplicationOutput.EnabledProtocols - Count = 1 - } + $contextMockWebApplicationOutput = $MockWebApplicationOutput.Clone() + $contextMockWebApplicationOutput.PreloadEnabled = $false - } - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput + Mock -CommandName Get-WebApplication -MockWith { + return $contextMockWebApplicationOutput } - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock -CommandName Add-WebConfiguration - Mock -CommandName New-WebApplication - Mock -CommandName Set-WebConfigurationProperty - Mock -CommandName Set-WebConfiguration - Mock -CommandName Set-ItemProperty + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } - It 'should call expected mocks' { + Mock -CommandName Set-ItemProperty + It 'Should call expected mocks' { Set-TargetResource -Ensure 'Present' @MockParameters Assert-MockCalled -CommandName Get-WebApplication -Exactly 1 + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Test-AuthenticationEnabled -Exactly 4 Assert-MockCalled -CommandName Set-ItemProperty -Exactly 1 } - } Context 'Web Application exists but has ServiceAutoStartEnabled not set' { - Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockWebApplicationOutput.applicationPool - PhysicalPath = $MockWebApplicationOutput.PhysicalPath - ItemXPath = ("/system.applicationHost/sites/site[@name='{0}']/application[@path='/{1}']" -f $MockWebApplicationOutput.Website, $MockWebApplicationOutput.Name) - PreloadEnabled = $MockWebApplicationOutput.PreloadEnabled - ServiceAutoStartEnabled = 'false' - ServiceAutoStartProvider = $MockWebApplicationOutput.ServiceAutoStartProvider - ApplicationType = $MockWebApplicationOutput.ApplicationType - EnabledProtocols = $MockWebApplicationOutput.EnabledProtocols - Count = 1 - } + $contextMockWebApplicationOutput = $MockWebApplicationOutput.Clone() + $contextMockWebApplicationOutput.ServiceAutoStartEnabled = $false + Mock -CommandName Get-WebApplication -MockWith { + return $contextMockWebApplicationOutput } - Mock -CommandName Get-WebConfiguration -ParameterFilter {$filter -eq 'system.webserver/security/access'} -MockWith { - return $GetWebConfigurationOutput - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } - Mock -CommandName Add-WebConfiguration - Mock -CommandName New-WebApplication - Mock -CommandName Set-WebConfigurationProperty - Mock -CommandName Set-WebConfiguration - Mock -CommandName Set-ItemProperty + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } - It 'should call expected mocks' { + Mock -CommandName Set-ItemProperty + It 'Should call expected mocks' { Set-TargetResource -Ensure 'Present' @MockParameters Assert-MockCalled -CommandName Get-WebApplication -Exactly 1 + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Test-AuthenticationEnabled -Exactly 4 Assert-MockCalled -CommandName Set-ItemProperty -Exactly 1 } - } Context 'Web Application exists but has different ServiceAutoStartProvider' { - $GetWebConfigurationOutput = @( - @{ - SectionPath = 'MockSectionPath' - PSPath = 'MockPSPath' - SslFlags = 'Ssl' - Collection = @( - [PSCustomObject]@{Name = 'OtherMockServiceAutoStartProvider' ;Type = 'OtherMockApplicationType'} - ) - } - ) + $contextMockWebApplicationOutput = $MockWebApplicationOutput.Clone() + $contextMockWebApplicationOutput.ServiceAutoStartProvider = 'OtherServiceAutoStartProvider' + $contextMockWebApplicationOutput.ApplicationType = 'OtherApplicationType' Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockWebApplicationOutput.applicationPool - PhysicalPath = $MockWebApplicationOutput.PhysicalPath - ItemXPath = ("/system.applicationHost/sites/site[@name='{0}']/application[@path='/{1}']" -f $MockWebApplicationOutput.Website, $MockWebApplicationOutput.Name) - PreloadEnabled = $MockWebApplicationOutput.PreloadEnabled - ServiceAutoStartEnabled = $MockWebApplicationOutput.ServiceAutoStartEnabled - ServiceAutoStartProvider = 'OtherServiceAutoStartProvider' - ApplicationType = 'OtherApplicationType' - EnabledProtocols = $MockWebApplicationOutput.EnabledProtocols - Count = 1 - } - + return $contextMockWebApplicationOutput } - Mock -CommandName Get-WebConfiguration -MockWith { - return $GetWebConfigurationOutput - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Mock -ModuleName $DSCHelperModuleName -CommandName Get-WebConfiguration - Mock -CommandName Add-WebConfiguration - Mock -CommandName New-WebApplication - Mock -CommandName Set-WebConfigurationProperty - Mock -CommandName Set-WebConfiguration - Mock -CommandName Set-ItemProperty + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } + + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } - It 'should call expected mocks' { + Mock -CommandName Set-ItemProperty + Mock -CommandName Add-WebConfiguration + It 'Should call expected mocks' { Set-TargetResource -Ensure 'Present' @MockParameters Assert-MockCalled -CommandName Get-WebApplication -Exactly 1 - Assert-MockCalled -CommandName Set-ItemProperty -Exactly 1 + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Test-AuthenticationEnabled -Exactly 4 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Get-WebConfiguration -Exactly 1 Assert-MockCalled -CommandName Add-WebConfiguration -Exactly 1 + Assert-MockCalled -CommandName Set-ItemProperty -Exactly 1 } - } Context 'Web Application exists but has different EnabledProtocols' { - $GetWebConfigurationOutput = @( - @{ - SectionPath = 'MockSectionPath' - PSPath = 'MockPSPath' - SslFlags = 'Ssl' - Collection = @( - [PSCustomObject]@{Name = 'OtherMockServiceAutoStartProvider' ;Type = 'OtherMockApplicationType'} - ) - } - ) + $contextMockWebApplicationOutput = $MockWebApplicationOutput.Clone() + $contextMockWebApplicationOutput.EnabledProtocols = 'http,net.tcp' Mock -CommandName Get-WebApplication -MockWith { - return @{ - ApplicationPool = $MockWebApplicationOutput.applicationPool - PhysicalPath = $MockWebApplicationOutput.PhysicalPath - ItemXPath = ("/system.applicationHost/sites/site[@name='{0}']/application[@path='/{1}']" -f $MockWebApplicationOutput.Website, $MockWebApplicationOutput.Name) - PreloadEnabled = $MockWebApplicationOutput.PreloadEnabled - ServiceAutoStartEnabled = $MockWebApplicationOutput.ServiceAutoStartEnabled - ServiceAutoStartProvider = $MockWebApplicationOutput.ServiceAutoStartProvider - ApplicationType = $MockWebApplicationOutput.ApplicationType - EnabledProtocols = 'http,net.tcp' - Count = 1 - } - + return $contextMockWebApplicationOutput } - Mock -CommandName Get-WebConfiguration -MockWith { - return $GetWebConfigurationOutput - } + Mock -CommandName Get-WebConfiguration ` + -ParameterFilter {$filter -eq 'system.webserver/security/access'} ` + -MockWith { return $GetWebConfigurationOutput } - Mock -CommandName Get-WebConfigurationProperty -MockWith { - return $MockAuthenticationInfo - } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Windows')) } - Mock -CommandName Add-WebConfiguration - Mock -CommandName New-WebApplication - Mock -CommandName Set-WebConfigurationProperty - Mock -CommandName Set-WebConfiguration - Mock -CommandName Set-ItemProperty + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } - It 'should call expected mocks' { + Mock -CommandName Set-ItemProperty + It 'Should call expected mocks' { Set-TargetResource -Ensure 'Present' @MockParameters Assert-MockCalled -CommandName Get-WebApplication -Exactly 1 + Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Test-AuthenticationEnabled -Exactly 4 Assert-MockCalled -CommandName Set-ItemProperty -Exactly 1 } - } - } - Describe "$script:DSCResourceName\Confirm-UniqueEnabledProtocols" { + Describe "$DSCResourceName\Confirm-UniqueEnabledProtocols" { Context 'Tests Confirm-UniqueEnabledProtocols' { It 'Should return true when settings match' { - Confirm-UniqueEnabledProtocols -ExistingProtocols 'http,net.tcp' ` -ProposedProtocols @('http','net.tcp') ` | Should be $true } It 'Should return false when settings do not match' { - Confirm-UniqueEnabledProtocols -ExistingProtocols 'http' ` -ProposedProtocols @('http','net.tcp') ` | Should be $false @@ -1072,419 +939,44 @@ try } } - Describe "$script:DSCResourceName\Confirm-UniqueServiceAutoStartProviders" { - - $MockParameters = @{ - Name = 'MockServiceAutoStartProvider' - Type = 'MockApplicationType' - } - - Context 'Expected behavior' { - - $GetWebConfigurationOutput = @( - @{ - SectionPath = 'MockSectionPath' - PSPath = 'MockPSPath' - Collection = @( - [PSCustomObject]@{Name = 'MockServiceAutoStartProvider' ;Type = 'MockApplicationType'} - ) - } - ) - - Mock -CommandName Get-WebConfiguration -MockWith {return $GetWebConfigurationOutput} - - It 'should not throw an error' { - {Confirm-UniqueServiceAutoStartProviders -ServiceAutoStartProvider $MockParameters.Name -ApplicationType 'MockApplicationType'} | - Should Not Throw - } - - It 'should call Get-WebConfiguration once' { - Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 - } - - } - - Context 'Conflicting Global Property' { - - $GetWebConfigurationOutput = @( - @{ - SectionPath = 'MockSectionPath' - PSPath = 'MockPSPath' - Collection = @( - [PSCustomObject]@{Name = 'MockServiceAutoStartProvider' ;Type = 'MockApplicationType'} - ) - } - ) - - Mock -CommandName Get-WebConfiguration -MockWith {return $GetWebConfigurationOutput} - - It 'should return Throw' { - - $ErrorId = 'ServiceAutoStartProviderFailure' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation - $ErrorMessage = $LocalizedData.ErrorWebApplicationTestAutoStartProviderFailure, 'ScriptHalted' - $Exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $ErrorMessage - $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - {Confirm-UniqueServiceAutoStartProviders -ServiceAutoStartProvider $MockParameters.Name -ApplicationType 'MockApplicationType2'} | - Should Throw $ErrorRecord - } - - } - - Context 'ServiceAutoStartProvider does not exist' { - - $GetWebConfigurationOutput = @( - @{ - Name = '' - Type = '' - } - ) - - Mock -CommandName Get-WebConfiguration -MockWith {return $GetWebConfigurationOutput} - - It 'should return False' { - Confirm-UniqueServiceAutoStartProviders -ServiceAutoStartProvider $MockParameters.Name -ApplicationType 'MockApplicationType' | - Should Be $false - } - - } - - Context 'ServiceAutoStartProvider does exist' { - - $GetWebConfigurationOutput = @( - @{ - SectionPath = 'MockSectionPath' - PSPath = 'MockPSPath' - Collection = @( - [PSCustomObject]@{Name = 'MockServiceAutoStartProvider' ;Type = 'MockApplicationType'} - ) - } - ) - - Mock -CommandName Get-WebConfiguration -MockWith {return $GetWebConfigurationOutput} - - It 'should return True' { - Confirm-UniqueServiceAutoStartProviders -ServiceAutoStartProvider $MockParameters.Name -ApplicationType 'MockApplicationType' | - Should Be $true - } - - } - - } - - Describe "$script:DSCResourceName\Get-AuthenticationInfo" { - - Context 'Expected behavior' { - - Mock -CommandName Get-WebConfigurationProperty -MockWith { return 'False'} - - It 'should not throw an error' { - { Get-AuthenticationInfo -site $MockParameters.Website -name $MockParameters.Name } | - Should Not Throw - } - - It 'should call Get-WebConfigurationProperty four times' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 4 - } - - } - - Context 'AuthenticationInfo is false' { - - $GetWebConfigurationOutput = @( - @{ - Value = $false - } - ) - - Mock -CommandName Get-WebConfigurationProperty -MockWith { $GetWebConfigurationOutput} - - - It 'should all be false' { - $result = Get-AuthenticationInfo -site $MockParameters.Website -name $MockParameters.Name - $result.Anonymous | Should be $false - $result.Digest | Should be $false - $result.Basic | Should be $false - $result.Windows | Should be $false - } - - It 'should call Get-WebConfigurationProperty four times' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 4 - } - - } - - Context 'AuthenticationInfo is true' { - - $GetWebConfigurationOutput = @( - @{ - Value = 'True' - } - ) - - Mock -CommandName Get-WebConfigurationProperty -MockWith { $GetWebConfigurationOutput} - - It 'should all be true' { - $result = Get-AuthenticationInfo -site $MockParameters.Website -name $MockParameters.Name - $result.Anonymous | Should be True - $result.Digest | Should be True - $result.Basic | Should be True - $result.Windows | Should be True - } - - It 'should call Get-WebConfigurationProperty four times' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 4 - } - - } - - } - - Describe "$script:DSCResourceName\Get-DefaultAuthenticationInfo" { + Describe "$DSCResourceName\Get-SslFlags" { Context 'Expected behavior' { - It 'should not throw an error' { - { Get-DefaultAuthenticationInfo }| - Should Not Throw - } - - } - - Context 'Get-DefaultAuthenticationInfo should produce a false CimInstance' { + Mock -CommandName Get-WebConfiguration -MockWith { return $GetWebConfigurationOutput } - It 'should all be false' { - $result = Get-DefaultAuthenticationInfo - $result.Anonymous | Should be False - $result.Digest | Should be False - $result.Basic | Should be False - $result.Windows | Should be False - } - - } - - } - - Describe "$script:DSCResourceName\Get-SslFlags" { - - Context 'Expected behavior' { - - Mock -CommandName Get-WebConfiguration -MockWith {$GetWebConfigurationOutput} - - It 'should not throw an error' { + It 'Should not throw an error' { { Get-SslFlags -Location (${MockParameters}.Website + '\' + ${MockParameters}.Name) }| Should Not Throw } - It 'should call Get-WebConfiguration once' { + It 'Should call Get-WebConfiguration once' { Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 } - } Context 'SslFlags do not exist' { Mock -CommandName Get-WebConfiguration -MockWith {return ''} - It 'should return nothing' { + It 'Should return nothing' { Get-SslFlags -Location (${MockParameters}.Website + '\' + ${MockParameters}.Name) | Should BeNullOrEmpty } - } Context 'SslFlags do exist' { - Mock -CommandName Get-WebConfiguration -MockWith {$GetWebConfigurationOutput} + Mock -CommandName Get-WebConfiguration -MockWith { return $GetWebConfigurationOutput } - It 'should return SslFlags' { + It 'Should return SslFlags' { Get-SslFlags -Location (${MockParameters}.Website + '\' + ${MockParameters}.Name) | Should Be 'Ssl' } - - } - - } - - Describe "$script:DSCResourceName\Set-Authentication" { - - Context 'Expected behavior' { - - Mock -CommandName Set-WebConfigurationProperty - - It 'should not throw an error' { - { Set-Authentication -Site $MockParameters.Website -Name $MockParameters.Name -Type Basic -Enabled $true }| - Should Not Throw - } - - It 'should call Set-WebConfigurationProperty once' { - Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 1 - } - - } - - } - - Describe "$script:DSCResourceName\Set-AuthenticationInfo" { - - Context 'Expected behavior' { - - Mock -CommandName Set-WebConfigurationProperty - - $AuthenticationInfo = New-CimInstance -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly ` - -Property @{Anonymous='true';Basic='false';Digest='false';Windows='false'} - - It 'should not throw an error' { - { Set-AuthenticationInfo -Site $MockParameters.Website -Name $MockParameters.Name -AuthenticationInfo $AuthenticationInfo }| - Should Not Throw - } - - It 'should call should call expected mocks' { - Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 4 - } - - } - - } - - Describe "$script:DSCResourceName\Test-AuthenticationEnabled" { - - Context 'Expected behavior' { - - $GetWebConfigurationOutput = @( - @{ - Value = 'False' - } - ) - - Mock -CommandName Get-WebConfigurationProperty -MockWith {$GetWebConfigurationOutput} - - It 'should not throw an error' { - { Test-AuthenticationEnabled -Site $MockParameters.Website -Name $MockParameters.Name -Type 'Basic'}| - Should Not Throw - } - - It 'should call expected mocks' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 1 - } - - } - - Context 'AuthenticationInfo is false' { - - $GetWebConfigurationOutput = @( - @{ - Value = 'False' - } - ) - - Mock -CommandName Get-WebConfigurationProperty -MockWith { $GetWebConfigurationOutput} - - - It 'should return false' { - Test-AuthenticationEnabled -site $MockParameters.Website -name $MockParameters.Name -Type 'Basic' | Should be False - } - - It 'should call expected mocks' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 1 - } - } - - Context 'AuthenticationInfo is true' { - - $GetWebConfigurationOutput = @( - @{ - Value = 'True' - } - ) - - Mock -CommandName Get-WebConfigurationProperty -MockWith { $GetWebConfigurationOutput} - - It 'should all be true' { - Test-AuthenticationEnabled -site $MockParameters.Website -name $MockParameters.Name -Type 'Basic' | Should be True - } - - It 'should call expected mocks' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 1 - } - - } - } - Describe "$script:DSCResourceName\Test-AuthenticationInfo" { - - Mock -CommandName Get-WebConfigurationProperty -MockWith {$GetWebConfigurationOutput} - - $GetWebConfigurationOutput = @( - @{ - Value = 'False' - } - ) - - $AuthenticationInfo = New-CimInstance -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly ` - -Property @{Anonymous='false';Basic='true';Digest='false';Windows='false'} - - Context 'Expected behavior' { - - - It 'should not throw an error' { - { Test-AuthenticationInfo -Site $MockParameters.Website -Name $MockParameters.Name -AuthenticationInfo $AuthenticationInfo }| - Should Not Throw - } - - It 'should call expected mocks' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 2 - } - - } - - Context 'Return False when AuthenticationInfo is not correct' { - - Mock -CommandName Get-WebConfigurationProperty -MockWith { $GetWebConfigurationOutput} - - - It 'should return false' { - Test-AuthenticationInfo -site $MockParameters.Website -name $MockParameters.Name -AuthenticationInfo $AuthenticationInfo | Should be False - } - - It 'should call expected mocks' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 2 - } - - } - - Context 'Return True when AuthenticationInfo is correct' { - - $GetWebConfigurationOutput = @( - @{ - Value = 'True' - } - ) - - $AuthenticationInfo = New-CimInstance -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly ` - -Property @{Anonymous='true';Basic='true';Digest='true';Windows='true'} - - Mock -CommandName Get-WebConfigurationProperty -MockWith { $GetWebConfigurationOutput} - - It 'should return true' { - Test-AuthenticationInfo -site $MockParameters.Website -name $MockParameters.Name -AuthenticationInfo $AuthenticationInfo | Should be True - } - - It 'should call expected mocks' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 4 - } - - } - - } - - Describe "$script:DSCResourceName\Test-SslFlags" { + Describe "$DSCResourceName\Test-SslFlags" { Context 'Expected behavior' { @@ -1492,15 +984,14 @@ try return $GetWebConfigurationOutput } - It 'should not throw an error' { + It 'Should not throw an error' { { Test-SslFlags -Location ${MockParameters.Website}/${MockParameters.Name} -SslFlags $MockParameters.SslFlags }| Should Not Throw } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 } - } Context 'Return False when SslFlags are not correct' { @@ -1515,15 +1006,13 @@ try return $GetWebConfigurationOutput } - - It 'should return false' { + It 'Should return false' { Test-SslFlags -Location ${MockParameters.Website}/${MockParameters.Name} -SslFlags $MockParameters.SslFlags | Should be False } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 } - } Context 'Return True when SslFlags are correct' { @@ -1532,20 +1021,17 @@ try return $GetWebConfigurationOutput } - It 'should return true' { + It 'Should return true' { Test-SslFlags -Location ${MockParameters.Website}/${MockParameters.Name} -SslFlags $MockParameters.SslFlags | Should be True } - It 'should call expected mocks' { + It 'Should call expected mocks' { Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 } - } - } - } - + #endregion } finally { diff --git a/Tests/Unit/MSFT_xWebsite.Tests.ps1 b/Tests/Unit/MSFT_xWebsite.Tests.ps1 index 1f401ea71..18afb27d2 100644 --- a/Tests/Unit/MSFT_xWebsite.Tests.ps1 +++ b/Tests/Unit/MSFT_xWebsite.Tests.ps1 @@ -1,6 +1,6 @@ - -$script:DSCModuleName = 'xWebAdministration' -$script:DSCResourceName = 'MSFT_xWebsite' +$script:DSCModuleName = 'xWebAdministration' +$script:DSCResourceName = 'MSFT_xWebsite' +$script:DSCHelperModuleName = 'Helper' #region HEADER $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) @@ -12,10 +12,9 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force -$TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit +$TestEnvironment = Initialize-TestEnvironment -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit #endregion # Begin Testing @@ -23,7 +22,8 @@ try { #region Pester Tests InModuleScope -ModuleName $script:DSCResourceName -ScriptBlock { - $script:DSCResourceName = 'MSFT_xWebsite' + $script:DSCResourceName = 'MSFT_xWebsite' + $script:DSCHelperModuleName = 'Helper' # Make sure we don't have the original module in memory. Remove-Module -Name 'WebAdministration' -ErrorAction SilentlyContinue @@ -32,110 +32,98 @@ try $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\MockWebAdministrationWindowsFeature.psm1') -Force - Describe "$script:DSCResourceName\Assert-Module" { - Context 'WebAdminstration module is not installed' { - Mock -ModuleName Helper -CommandName Get-Module -MockWith { return $null } - - It 'should throw an error' { - { Assert-Module } | Should Throw - } + $MockAuthenticationInfo = New-CimInstance ` + -ClassName MSFT_xWebApplicationAuthenticationInformation ` + -ClientOnly ` + -Property @{Anonymous=$true;Basic=$false;Digest=$false;Windows=$false} ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + + $MockWebBinding = @( + @{ + bindingInformation = '*:443:web01.contoso.com' + protocol = 'https' + certificateHash = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' + certificateStoreName = 'WebHosting' + sslFlags = '1' + } + ) + + $MockPreloadAndAutostartProviders = @( + @{ + preloadEnabled = 'True' + ServiceAutoStartProvider = 'MockServiceAutoStartProvider' + ServiceAutoStartEnabled = 'True' } + ) + + $mockLogCustomFields = @( + @{ + LogFieldName = 'LogField1' + SourceName = 'Accept-Encoding' + SourceType = 'RequestHeader' + } + @{ + LogFieldName = 'LogField2' + SourceName = 'Warning' + SourceType = 'ResponseHeader' + } + ) + + $MockLogOutput = @{ + directory = '%SystemDrive%\inetpub\logs\LogFiles' + logExtFileFlags = 'Date','Time','ClientIP','UserName','ServerIP','Method','UriStem','UriQuery','HttpStatus','Win32Status','TimeTaken','ServerPort','UserAgent','Referer','HttpSubStatus' + logFormat = $MockParameters.LogFormat + period = 'Daily' + logTargetW3C = 'File,ETW' + truncateSize = '1048576' + localTimeRollover = 'False' + customFields = @{Collection = $mockLogCustomFields} } - Describe "how $script:DSCResourceName\Get-TargetResource responds" { - $MockWebBinding = @( - @{ - bindingInformation = '*:443:web01.contoso.com' - protocol = 'https' - certificateHash = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - certificateStoreName = 'WebHosting' - sslFlags = '1' - } - ) - - $MockPreloadAndAutostartProviders = @( - @{ - preloadEnabled = 'True' - ServiceAutoStartProvider = 'MockServiceAutoStartProvider' - ServiceAutoStartEnabled = 'True' - } - ) - - $MockWebConfiguration = @( - @{ - SectionPath = 'MockSectionPath' - PSPath = 'MockPSPath' - Collection = @( - [PSCustomObject] @{ - Name = 'MockServiceAutoStartProvider'; - Type = 'MockApplicationType' - } - ) - } - ) - - $MockAuthenticationInfo = @( - New-CimInstance -ClassName MSFT_xWebAuthenticationInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Anonymous = 'true' - Basic = 'false' - Digest = 'false' - Windows = 'false' - } ` - -ClientOnly - ) - - $mockLogCustomFields = @( - @{ - LogFieldName = 'LogField1' - SourceName = 'Accept-Encoding' - SourceType = 'RequestHeader' - } - @{ - LogFieldName = 'LogField2' - SourceName = 'Warning' - SourceType = 'ResponseHeader' - } - ) + $MockWebsite = @{ + Name = 'MockName' + Id = 1234 + PhysicalPath = 'C:\NonExistent' + State = 'Started' + ApplicationPool = 'MockPool' + AuthenticationInfo = $MockAuthenticationInfo + Bindings = @{Collection = @($MockWebBinding)} + EnabledProtocols = 'http' + ApplicationDefaults = $MockPreloadAndAutostartProviders + LogFile = $MockLogOutput + Count = 1 + } - $MockLogOutput = @{ - directory = '%SystemDrive%\inetpub\logs\LogFiles' - logExtFileFlags = 'Date','Time','ClientIP','UserName','ServerIP','Method','UriStem','UriQuery','HttpStatus','Win32Status','TimeTaken','ServerPort','UserAgent','Referer','HttpSubStatus' - logFormat = $MockParameters.LogFormat - period = 'Daily' - logTargetW3C = 'File,ETW' - truncateSize = '1048576' - localTimeRollover = 'False' - customFields = @{Collection = $mockLogCustomFields} + $MockWebConfiguration = @( + @{ + SectionPath = 'MockSectionPath' + PSPath = 'MockPSPath' + Collection = @( + [PSCustomObject] @{ + Name = 'MockServiceAutoStartProvider'; + Type = 'MockApplicationType' + } + ) } + ) - $MockWebsite = @{ - Name = 'MockName' - Id = 1234 - PhysicalPath = 'C:\NonExistent' - State = 'Started' - ApplicationPool = 'MockPool' - Bindings = @{Collection = @($MockWebBinding)} - EnabledProtocols = 'http' - ApplicationDefaults = $MockPreloadAndAutostartProviders - LogFile = $MockLogOutput - Count = 1 - } + Describe "how $DSCResourceName\Get-TargetResource responds" { Mock -CommandName Assert-Module -MockWith {} Context 'Website does not exist' { + Mock -CommandName Get-Website $Result = Get-TargetResource -Name $MockWebsite.Name - It 'should return Absent' { + It 'Should return Absent' { $Result.Ensure | Should Be 'Absent' } } Context 'There are multiple websites with the same name' { + Mock -CommandName Get-Website -MockWith { return @( @{Name = 'MockName'} @@ -143,22 +131,21 @@ try ) } - It 'should throw the correct error' { - $ErrorId = 'WebsiteDiscoveryFailure' + It 'Should throw the correct error' { + $ErrorId = 'WebsiteDiscoveryFailure' $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult - $ErrorMessage = $LocalizedData.ErrorWebsiteDiscoveryFailure -f 'MockName' - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + $ErrorMessage = $LocalizedData.ErrorWebsiteDiscoveryFailure -f 'MockName' + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null {Get-TargetResource -Name 'MockName'} | Should Throw $ErrorRecord } } Context 'Single website exists' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} Mock -CommandName Get-WebConfiguration ` @@ -169,56 +156,58 @@ try -ParameterFilter {$filter -eq '/system.applicationHost/serviceAutoStartProviders'} ` -MockWith { return $MockWebConfiguration} - Mock -CommandName Get-WebConfigurationProperty ` - -MockWith {return $MockAuthenticationInfo} - - Mock -CommandName Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Anonymous') } - - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $true } ` + -ParameterFilter { ($Type -in ('Anonymous', 'Windows')) } - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } - - Mock -CommandName Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Windows') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -MockWith { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest')) } $Result = Get-TargetResource -Name $MockWebsite.Name - It 'should call Get-Website once' { + It 'Should call Get-Website once' { Assert-MockCalled -CommandName Get-Website -Exactly 1 } - It 'should call Get-WebConfiguration twice' { + It 'Should call Get-WebConfiguration twice' { Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 2 } - It 'should return Ensure' { + It 'Should call Test-AuthenticationEnabled four times' { + Assert-MockCalled ` + -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -Exactly 4 + } + + It 'Should return Ensure' { $Result.Ensure | Should Be 'Present' } - It 'should return Name' { + It 'Should return Name' { $Result.Name | Should Be $MockWebsite.Name } - It 'should return SiteId' { + It 'Should return SiteId' { $Result.SiteId | Should Be $MockWebsite.Id } - It 'should return PhysicalPath' { + It 'Should return PhysicalPath' { $Result.PhysicalPath | Should Be $MockWebsite.PhysicalPath } - It 'should return State' { + It 'Should return State' { $Result.State | Should Be $MockWebsite.State } - It 'should return ApplicationPool' { + It 'Should return ApplicationPool' { $Result.ApplicationPool | Should Be $MockWebsite.ApplicationPool } - It 'should return BindingInfo' { + It 'Should return BindingInfo' { $Result.BindingInfo.Protocol | Should Be $MockWebBinding.protocol $Result.BindingInfo.BindingInformation | Should Be $MockWebBinding.bindingInformation $Result.BindingInfo.IPAddress | Should Be '*' @@ -229,66 +218,66 @@ try $Result.BindingInfo.SslFlags | Should Be $MockWebBinding.sslFlags } - It 'should return DefaultPage' { + It 'Should return DefaultPage' { $Result.DefaultPage | Should Be 'index.html' } - It 'should return EnabledProtocols' { + It 'Should return EnabledProtocols' { $Result.EnabledProtocols | Should Be $MockWebsite.EnabledProtocols } - It 'should return AuthenticationInfo' { - $Result.AuthenticationInfo.CimInstanceProperties['Anonymous'].Value | Should Be 'true' - $Result.AuthenticationInfo.CimInstanceProperties['Basic'].Value | Should Be 'false' - $Result.AuthenticationInfo.CimInstanceProperties['Digest'].Value | Should Be 'false' - $Result.AuthenticationInfo.CimInstanceProperties['Windows'].Value | Should Be 'true' + It 'Should return AuthenticationInfo' { + $Result.AuthenticationInfo.CimInstanceProperties['Anonymous'].Value | Should Be $true + $Result.AuthenticationInfo.CimInstanceProperties['Basic'].Value | Should Be $false + $Result.AuthenticationInfo.CimInstanceProperties['Digest'].Value | Should Be $false + $Result.AuthenticationInfo.CimInstanceProperties['Windows'].Value | Should Be $true } - It 'should return Preload' { + It 'Should return Preload' { $Result.PreloadEnabled | Should Be $MockWebsite.ApplicationDefaults.PreloadEnabled } - It 'should return ServiceAutoStartProvider' { + It 'Should return ServiceAutoStartProvider' { $Result.ServiceAutoStartProvider | Should Be $MockWebsite.ApplicationDefaults.ServiceAutoStartProvider } - It 'should return ServiceAutoStartEnabled' { + It 'Should return ServiceAutoStartEnabled' { $Result.ServiceAutoStartEnabled | Should Be $MockWebsite.ApplicationDefaults.ServiceAutoStartEnabled } - It 'should return ApplicationType' { + It 'Should return ApplicationType' { $Result.ApplicationType | Should Be $MockPreloadAndAutostartProvider.ApplicationType } - It 'should return correct LogPath' { + It 'Should return correct LogPath' { $Result.LogPath | Should Be $MockWebsite.Logfile.directory } - It 'should return LogFlags' { + It 'Should return LogFlags' { $Result.LogFlags | Should Be $MockWebsite.Logfile.logExtFileFlags } - It 'should return LogPeriod' { + It 'Should return LogPeriod' { $Result.LogPeriod | Should Be $MockWebsite.Logfile.period } - It 'should return LogTargetW3C' { + It 'Should return LogTargetW3C' { $Result.TargetW3C | Should Be $MockWebsite.Logfile.TargetW3C } - It 'should return LogTruncateSize' { + It 'Should return LogTruncateSize' { $Result.LogTruncateSize | Should Be $MockWebsite.Logfile.truncateSize } - It 'should return LoglocalTimeRollover' { + It 'Should return LoglocalTimeRollover' { $Result.LoglocalTimeRollover | Should Be $MockWebsite.Logfile.localTimeRollover } - It 'should return LogFormat' { + It 'Should return LogFormat' { $Result.logFormat | Should Be $MockWebsite.Logfile.logFormat } - It 'should return LogCustomFields' { + It 'Should return LogCustomFields' { $Result.LogCustomFields[0].LogFieldName | Should Be $mockLogCustomFields[0].LogFieldName $Result.LogCustomFields[0].SourceName | Should Be $mockLogCustomFields[0].SourceName $Result.LogCustomFields[0].SourceType | Should Be $mockLogCustomFields[0].SourceType @@ -299,24 +288,26 @@ try } } - Describe "how $script:DSCResourceName\Test-TargetResource responds to Ensure = 'Present'" { + Describe "how $DSCResourceName\Test-TargetResource responds to Ensure = 'Present'" { + $MockBindingInfo = @( New-CimInstance -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'https' - IPAddress = '*' - Port = 443 - HostName = 'web01.contoso.com' - CertificateThumbprint = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - CertificateStoreName = 'WebHosting' - SslFlags = 1 - } -ClientOnly + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + IPAddress = '*' + Port = 443 + HostName = 'web01.contoso.com' + CertificateThumbprint = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' + CertificateStoreName = 'WebHosting' + SslFlags = 1 + } ) $MockCimLogCustomFields = @( (New-CimInstance -ClassName MSFT_xLogCustomFieldInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` -Property @{ LogFieldName = 'LogField1' SourceName = 'Accept-Encoding' @@ -325,7 +316,7 @@ try -ClientOnly ), (New-CimInstance -ClassName MSFT_xLogCustomFieldInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` -Property @{ LogFieldName = 'LogField2' SourceName = 'Warning' @@ -335,12 +326,19 @@ try ) ) + $MockAuthenticationInfo = New-CimInstance ` + -ClassName MSFT_xWebAuthenticationInformation ` + -ClientOnly ` + -Property @{ Anonymous=$true; Basic=$false; Digest=$false; Windows=$true } ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' + $MockParameters = @{ Ensure = 'Present' Name = 'MockName' PhysicalPath = 'C:\NonExistent' State = 'Started' ApplicationPool = 'MockPool' + AuthenticationInfo = $MockAuthenticationInfo BindingInfo = $MockBindingInfo DefaultPage = @('index.html') EnabledProtocols = 'http' @@ -415,6 +413,7 @@ try Mock -CommandName Assert-Module -MockWith {} Context 'Website does not exist' { + Mock -CommandName Get-Website $Result = Test-TargetResource ` @@ -422,171 +421,236 @@ try -Name $MockParameters.Name ` -PhysicalPath $MockParameters.PhysicalPath - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } } + Context 'Check SiteId is different' { + Mock -CommandName Get-Website -MockWith {$MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` -Name $MockParameters.Name ` -SiteId 12345 ` -Verbose:$VerbosePreference - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } } Context 'Check PhysicalPath is different' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` -Name $MockParameters.Name ` -PhysicalPath 'C:\Different' ` -Verbose:$VerbosePreference - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } } Context 'Check State is different' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` -Name $MockParameters.Name ` -PhysicalPath $MockParameters.PhysicalPath ` -State 'Stopped' ` -Verbose:$VerbosePreference - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } } Context 'Check ApplicationPool is different' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - $Result = Test-TargetResource -Name $MockParameters.Name ` + $Result = Test-TargetResource ` + -Name $MockParameters.Name ` -Ensure $MockParameters.Ensure ` -PhysicalPath $MockParameters.PhysicalPath ` -ApplicationPool 'MockPoolDifferent' ` -Verbose:$VerbosePreference - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } } Context 'Check BindingInfo is different' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} - Mock -CommandName Test-WebsiteBinding -MockWith {return $false} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-WebsiteBinding ` + -MockWith {return $false} - $Result = Test-TargetResource -Name $MockParameters.Name ` + $Result = Test-TargetResource ` + -Name $MockParameters.Name ` -Ensure $MockParameters.Ensure ` -PhysicalPath $MockParameters.PhysicalPath ` -BindingInfo $MockParameters.BindingInfo ` -Verbose:$VerbosePreference - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } } Context 'Check DefaultPage is different' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } Mock -CommandName Get-WebConfiguration -MockWith {return @{value = 'MockDifferent.html'}} - $Result = Test-TargetResource -Name $MockParameters.Name ` + $Result = Test-TargetResource ` + -Name $MockParameters.Name ` -Ensure $MockParameters.Ensure ` -PhysicalPath $MockParameters.PhysicalPath ` -DefaultPage $MockParameters.DefaultPage ` -Verbose:$VerbosePreference - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } } Context 'Check EnabledProtocols is different' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` -Name $MockParameters.Name ` -PhysicalPath $MockParameters.PhysicalPath ` -EnabledProtocols 'https' ` -Verbose:$VerbosePreference - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } } Context 'Check AuthenticationInfo is different' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} - Mock Test-AuthenticationEnabled { return $true } ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $true } ` -ParameterFilter { ($Type -eq 'Anonymous') } - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest', 'Windows')) } + + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath ` + -AuthenticationInfo $MockParameters.AuthenticationInfo ` + -Verbose:$VerbosePreference + + It 'Should return False' { + $Result | Should Be $false + } + + It 'Should call all the mocks' { + Assert-MockCalled -CommandName Get-Website -Exactly 1 + Assert-MockCalled ` + -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -Exactly 4 + } + } + + Context 'Check AuthenticationInfo is different from default' { - Mock Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } + Mock -CommandName Get-Website -MockWith {return $MockWebsite} - Mock Test-AuthenticationEnabled { return $false } ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $true } ` -ParameterFilter { ($Type -eq 'Windows') } - $MockAuthenticationInfo = New-CimInstance ` - -ClassName MSFT_xWebAuthenticationInformation ` - -ClientOnly ` - -Property @{ Anonymous=$true; Basic=$false; Digest=$false; Windows=$true } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $false } ` + -ParameterFilter { ($Type -in @('Anonymous', 'Basic', 'Digest')) } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` -Name $MockParameters.Name ` -PhysicalPath $MockParameters.PhysicalPath ` - -AuthenticationInfo $MockAuthenticationInfo ` -Verbose:$VerbosePreference - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } + + It 'Should call all the mocks' { + Assert-MockCalled -CommandName Get-Website -Exactly 1 + Assert-MockCalled ` + -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -Exactly 4 + } } Context 'Check Preload is different' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -PhysicalPath $MockParameters.PhysicalPath ` - -Preload $False ` - -Verbose:$VerbosePreference + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath ` + -Preload $False ` + -Verbose:$VerbosePreference - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } } Context 'Check AutoStartEnabled is different' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` -Name $MockParameters.Name ` -PhysicalPath $MockParameters.PhysicalPath ` -ServiceAutoStartEnabled $False ` -Verbose:$VerbosePreference - It 'should return False' { + It 'Should return False' { $Result | Should Be $false } } Context 'Check AutoStartProvider is different' { + Mock -CommandName Get-Website -MockWith { return $MockWebsite } + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - $result = Test-TargetResource -Ensure $MockParameters.Ensure ` + $result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` -Name $MockParameters.Name ` -PhysicalPath $MockParameters.PhysicalPath ` -ServiceAutoStartProvider 'MockAutoStartProviderDifferent' ` @@ -599,20 +663,22 @@ try } Context 'Check LogPath is equal' { + $MockLogOutput.directory = $MockParameters.LogPath Mock -CommandName Test-Path -MockWith { return $true } - Mock -CommandName Get-Website -MockWith { return $MockWebsite } + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } Mock -CommandName Get-WebConfigurationProperty ` -MockWith { return $MockLogOutput.logExtFileFlags } - $result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -PhysicalPath $MockParameters.PhysicalPath ` - -LogPath $MockParameters.LogPath ` - -Verbose:$VerbosePreference + $result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath ` + -LogPath $MockParameters.LogPath ` + -Verbose:$VerbosePreference It 'Should return true' { $result | Should be $true @@ -620,20 +686,23 @@ try } Context 'Check LogPath is different' { + $MockLogOutput.directory = $MockParameters.LogPath Mock -CommandName Test-Path -MockWith { return $true } - Mock -CommandName Get-Website -MockWith { return $MockWebsite } + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - Mock -CommandName Get-WebConfigurationProperty ` - -MockWith { return $MockLogOutput.logExtFileFlags } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfigurationProperty ` + -MockWith {return $MockLogOutput.logExtFileFlags } - $result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -PhysicalPath $MockParameters.PhysicalPath ` - -LogPath 'C:\MockLogPath2' ` - -Verbose:$VerbosePreference + $result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath ` + -LogPath 'C:\MockLogPath2' ` + -Verbose:$VerbosePreference It 'Should return false' { $result | Should be $false @@ -641,20 +710,23 @@ try } Context 'Check LogFlags are different' { + $MockLogOutput.logExtFileFlags = 'Date','Time','ClientIP','UserName','ServerIP','Method','UriStem','UriQuery','HttpStatus','Win32Status','TimeTaken','ServerPort','UserAgent','Referer','HttpSubStatus' Mock -CommandName Test-Path -MockWith { return $true } - Mock -CommandName Get-Website -MockWith { return $MockWebsite } + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - Mock -CommandName Get-WebConfigurationProperty ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfigurationProperty ` -MockWith { return $MockLogOutput.logExtFileFlags } - $result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -PhysicalPath $MockParameters.PhysicalPath ` - -LogFlags 'Date','Time','ClientIP','UserName','ServerIP' ` - -Verbose:$VerbosePreference + $result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath ` + -LogFlags 'Date','Time','ClientIP','UserName','ServerIP' ` + -Verbose:$VerbosePreference It 'Should return false' { $result | Should be $false @@ -662,20 +734,23 @@ try } Context 'Check LogPeriod is equal' { + $MockLogOutput.period = $MockParameters.LogPeriod Mock -CommandName Test-Path -MockWith {Return $true} - Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - Mock -CommandName Get-WebConfigurationProperty ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfigurationProperty ` -MockWith {return $MockLogOutput.logExtFileFlags } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -PhysicalPath $MockParameters.PhysicalPath ` - -LogPeriod 'Hourly' ` - -Verbose:$VerbosePreference + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath ` + -LogPeriod 'Hourly' ` + -Verbose:$VerbosePreference It 'Should return true' { $result | Should be $true @@ -683,20 +758,23 @@ try } Context 'Check LogPeriod is different' { + $MockLogOutput.period = 'Daily' Mock -CommandName Test-Path -MockWith {Return $true} - Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - Mock -CommandName Get-WebConfigurationProperty ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfigurationProperty ` -MockWith {return $MockLogOutput.logExtFileFlags } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -PhysicalPath $MockParameters.PhysicalPath ` - -LogPeriod 'Hourly' ` - -Verbose:$VerbosePreference + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath ` + -LogPeriod 'Hourly' ` + -Verbose:$VerbosePreference It 'Should return false' { $result | Should be $false @@ -704,6 +782,7 @@ try } Context 'Check LogTruncateSize is different' { + $MockLogOutput = @{ directory = $MockParameters.LogPath logExtFileFlags = $MockParameters.LogFlags @@ -714,17 +793,19 @@ try } Mock -CommandName Test-Path -MockWith {Return $true} - Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - Mock -CommandName Get-WebConfigurationProperty ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfigurationProperty ` -MockWith {return $MockLogOutput.logExtFileFlags } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -PhysicalPath $MockParameters.PhysicalPath ` - -LogTruncateSize '2000000' ` - -Verbose:$VerbosePreference + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath ` + -LogTruncateSize '2000000' ` + -Verbose:$VerbosePreference It 'Should return false' { $result | Should be $false @@ -732,6 +813,7 @@ try } Context 'Check LoglocalTimeRollover is different' { + $MockLogOutput = @{ directory = $MockParameters.LogPath logExtFileFlags = $MockParameters.LogFlags @@ -742,17 +824,19 @@ try } Mock -CommandName Test-Path -MockWith {Return $true} - Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - Mock -CommandName Get-WebConfigurationProperty ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfigurationProperty ` -MockWith {return $MockLogOutput.logExtFileFlags } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -PhysicalPath $MockParameters.PhysicalPath ` - -LoglocalTimeRollover $True ` - -Verbose:$VerbosePreference + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath ` + -LoglocalTimeRollover $True ` + -Verbose:$VerbosePreference It 'Should return false' { $result | Should be $false @@ -760,6 +844,7 @@ try } Context 'Check LogFormat is different' { + $MockLogOutput = @{ directory = $MockParameters.LogPath logExtFileFlags = $MockParameters.LogFlags @@ -782,17 +867,19 @@ try } Mock -CommandName Test-Path -MockWith {Return $true} - Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } - Mock -CommandName Get-WebConfigurationProperty ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfigurationProperty ` -MockWith {return $MockLogOutput.logExtFileFlags } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -PhysicalPath $MockParameters.PhysicalPath ` - -LogFormat 'W3C' ` - -Verbose:$VerbosePreference + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath ` + -LogFormat 'W3C' ` + -Verbose:$VerbosePreference It 'Should return false' { $result | Should be $false @@ -800,6 +887,7 @@ try } Context 'Check LogTargetW3C is different' { + $MockLogOutput = @{ directory = $MockParameters.LogPath logExtFileFlags = $MockParameters.LogFlags @@ -823,17 +911,18 @@ try } Mock -CommandName Test-Path -MockWith {Return $true} - Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } Mock -CommandName Get-WebConfigurationProperty ` -MockWith {return $MockLogOutput.logExtFileFlags } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -PhysicalPath $MockParameters.PhysicalPath ` - -LogTargetW3C 'File,ETW' ` - -Verbose:$VerbosePreference + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath ` + -LogTargetW3C 'File,ETW' ` + -Verbose:$VerbosePreference It 'Should return false' { $result | Should be $false @@ -841,6 +930,7 @@ try } Context 'Check LogTruncateSize is larger in string comparison' { + $MockLogOutput = @{ directory = $MockParameters.LogPath logExtFileFlags = $MockParameters.LogFlags @@ -851,17 +941,18 @@ try } Mock -CommandName Test-Path -MockWith { return $true } - Mock -CommandName Get-Website -MockWith { return $MockWebsite } + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } Mock -CommandName Get-WebConfigurationProperty ` -MockWith { return $MockLogOutput.logExtFileFlags } - $result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -PhysicalPath $MockParameters.PhysicalPath ` - -LogTruncateSize '5000000' ` - -Verbose:$VerbosePreference + $result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -PhysicalPath $MockParameters.PhysicalPath ` + -LogTruncateSize '5000000' ` + -Verbose:$VerbosePreference It 'Should return false' { $result | Should be $false @@ -869,19 +960,22 @@ try } Context 'Check LogCustomFields is equal' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } Mock -CommandName Get-WebConfigurationProperty ` -MockWith { return $mockLogCustomFields[0] } ` -ParameterFilter { $Filter -match $MockParameters.LogCustomFields[0].LogFieldName } Mock -CommandName Get-WebConfigurationProperty ` - -MockWith { return $mockLogCustomFields[1] } ` - -ParameterFilter { $Filter -match $MockParameters.LogCustomFields[1].LogFieldName } - - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -LogCustomFields $MockParameters.LogCustomFields + -MockWith { return $mockLogCustomFields[1] } ` + -ParameterFilter { $Filter -match $MockParameters.LogCustomFields[1].LogFieldName } + + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -LogCustomFields $MockParameters.LogCustomFields It 'Should return true' { $result | Should be $true @@ -889,7 +983,9 @@ try } Context 'Check LogCustomFields is different' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } $MockDifferentLogCustomFields = @{ LogFieldName = 'DifferentField' @@ -900,9 +996,10 @@ try Mock -CommandName Get-WebConfigurationProperty ` -MockWith {return $MockDifferentLogCustomFields } - $Result = Test-TargetResource -Ensure $MockParameters.Ensure ` - -Name $MockParameters.Name ` - -LogCustomFields $MockParameters.LogCustomFields + $Result = Test-TargetResource ` + -Ensure $MockParameters.Ensure ` + -Name $MockParameters.Name ` + -LogCustomFields $MockParameters.LogCustomFields It 'Should return false' { $result | Should be $false @@ -910,29 +1007,32 @@ try } } - Describe "how $script:DSCResourceName\Set-TargetResource responds to Ensure = 'Present'" { + Describe "how $DSCResourceName\Set-TargetResource responds to Ensure = 'Present'" { + $MockAuthenticationInfo = New-CimInstance ` - -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly ` - -Property @{ Anonymous=$true; Basic=$false; Digest=$false; Windows=$true } + -ClassName MSFT_xWebApplicationAuthenticationInformation ` + -ClientOnly ` + -Property @{ Anonymous=$true; Basic=$false; Digest=$false; Windows=$true } ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' $MockBindingInfo = @( New-CimInstance -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'https' - IPAddress = '*' - Port = 443 - HostName = 'web01.contoso.com' - CertificateThumbprint = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - CertificateStoreName = 'WebHosting' - SslFlags = 1 - } -ClientOnly + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + Protocol = 'https' + IPAddress = '*' + Port = 443 + HostName = 'web01.contoso.com' + CertificateThumbprint = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' + CertificateStoreName = 'WebHosting' + SslFlags = 1 + } ) $MockCimLogCustomFields = @( New-CimInstance -ClassName MSFT_xLogCustomFieldInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` -Property @{ LogFieldName = 'ClientEncoding' SourceName = 'Accept-Encoding' @@ -1010,27 +1110,28 @@ try } $MockWebsite = @{ - Name = 'MockName' - Id = 1234 - PhysicalPath = 'C:\Different' - State = 'Stopped' - ApplicationPool = 'MockPoolDifferent' - Bindings = @{ Collection = @($MockWebBinding) } - EnabledProtocols = 'http' - ApplicationDefaults = @{ Collection = @($MockPreloadAndAutostartProviders) } - LogFile = $MockLogOutput - } - - $MockWebsiteGetItem = $MockWebsite.Clone() + Name = 'MockName' + Id = 1234 + PhysicalPath = 'C:\Different' + State = 'Stopped' + ApplicationPool = 'MockPoolDifferent' + Bindings = @{ Collection = @($MockWebBinding) } + EnabledProtocols = 'http' + ApplicationDefaults = @{ Collection = @($MockPreloadAndAutostartProviders) } + LogFile = $MockLogOutput + } + + $MockWebsiteGetItem = $MockWebsite.Clone() $MockWebsiteGetItem.Path = 'WebAdministration::\\SERVERNAME\Sites\MockName' - $MockWebsiteGetItem = [PSCustomObject]$MockWebsiteGetItem + $MockWebsiteGetItem = [PSCustomObject]$MockWebsiteGetItem Mock -CommandName Assert-Module -MockWith {} Context 'All properties need to be updated and website must be started' { + Mock -CommandName Add-WebConfiguration - Mock -CommandName Confirm-UniqueBinding -MockWith { return $true } + Mock -CommandName Confirm-UniqueBinding -MockWith {return $true} Mock -CommandName Confirm-UniqueServiceAutoStartProviders -MockWith { return $false } @@ -1048,23 +1149,19 @@ try Mock -CommandName Set-WebConfiguration - Mock -CommandName Set-Authentication + Mock -ModuleName $DSCHelperModuleName -CommandName Set-Authentication Mock -CommandName Update-WebsiteBinding Mock -CommandName Update-DefaultPage - Mock -CommandName Test-AuthenticationEnabled { return $true } ` - -ParameterFilter { ($Type -eq 'Anonymous') } - - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } - - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $true } ` + -ParameterFilter { ($Type -eq 'Anonymous') } - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Windows') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $false } ` + -ParameterFilter { ($Type -in @('Basic','Digest','Windows')) } Mock -CommandName Set-WebConfigurationProperty @@ -1076,17 +1173,25 @@ try Assert-MockCalled -CommandName Add-WebConfiguration -Exactly 1 Assert-MockCalled -CommandName Confirm-UniqueBinding -Exactly 1 Assert-MockCalled -CommandName Confirm-UniqueServiceAutoStartProviders -Exactly 1 - Assert-MockCalled -CommandName Test-AuthenticationEnabled -Exactly 4 Assert-MockCalled -CommandName Test-WebsiteBinding -Exactly 1 Assert-MockCalled -CommandName Update-WebsiteBinding -Exactly 1 Assert-MockCalled -CommandName Update-DefaultPage -Exactly 1 - Assert-MockCalled -CommandName Set-Authentication -Exactly 4 Assert-MockCalled -CommandName Get-Item -Exactly 3 Assert-MockCalled -CommandName Set-Item -Exactly 3 Assert-MockCalled -CommandName Set-ItemProperty -Exactly 10 Assert-MockCalled -CommandName Start-Website -Exactly 1 Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 2 Assert-MockCalled -CommandName Test-LogCustomField -Exactly 1 + + Assert-MockCalled ` + -ModuleName $DSCHelperModuleName ` + -CommandName Set-Authentication ` + -Exactly 4 + + Assert-MockCalled ` + -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled ` + -Exactly 4 } } @@ -1122,13 +1227,19 @@ try Mock -CommandName Update-WebsiteBinding + Mock -CommandName Update-DefaultPage + + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } + $MockParametersNew = $MockParameters.Clone() $MockParametersNew.Remove('SiteId') It 'Should create and start the web site' { Set-TargetResource @MockParametersNew + Assert-MockCalled -CommandName New-Website -ParameterFilter { $Id -eq 1 } -Exactly 1 Assert-MockCalled -CommandName Start-Website -Exactly 1 + Assert-MockCalled -CommandName Update-DefaultPage -Exactly 1 } } @@ -1158,8 +1269,13 @@ try Mock -CommandName Update-WebsiteBinding + Mock -CommandName Update-DefaultPage + + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } + It 'Should create and start the web site' { Set-TargetResource @MockParameters + Assert-MockCalled -CommandName New-Website -ParameterFilter { $Id -eq 1234 } -Exactly 1 Assert-MockCalled -CommandName Start-Website -Exactly 1 } @@ -1191,11 +1307,16 @@ try Mock -CommandName Update-WebsiteBinding + Mock -CommandName Update-DefaultPage + + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } + $MockParameters = $MockParameters.Clone() $MockParameters.PhysicalPath = '' It 'Should create and start the web site' { Set-TargetResource @MockParameters + Assert-MockCalled -CommandName New-Website -ParameterFilter { $Force -eq $True } -Exactly 1 Assert-MockCalled -CommandName Start-Website -Exactly 1 } @@ -1227,17 +1348,23 @@ try Mock -CommandName Update-WebsiteBinding + Mock -CommandName Update-DefaultPage + + Mock -CommandName Test-AuthenticationInfo -MockWith { return $true } + $MockParameters = $MockParameters.Clone() $MockParameters.PhysicalPath = $null It 'Should create and start the web site' { Set-TargetResource @MockParameters + Assert-MockCalled -CommandName New-Website -ParameterFilter { $Force -eq $True } -Exactly 1 Assert-MockCalled -CommandName Start-Website -Exactly 1 } } Context 'Existing website cannot be started due to a binding conflict' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} Mock -CommandName Set-ItemProperty Mock -CommandName Add-WebConfiguration @@ -1248,22 +1375,21 @@ try Mock -CommandName Confirm-UniqueServiceAutoStartProviders -MockWith {return $true} Mock -CommandName Start-Website - It 'should throw the correct error' { - $ErrorId = 'WebsiteBindingConflictOnStart' + It 'Should throw the correct error' { + $ErrorId = 'WebsiteBindingConflictOnStart' $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult - $ErrorMessage = $LocalizedData.ErrorWebsiteBindingConflictOnStart -f $MockParameters.Name - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + $ErrorMessage = $LocalizedData.ErrorWebsiteBindingConflictOnStart -f $MockParameters.Name + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null {Set-TargetResource @MockParameters} | Should Throw $ErrorRecord } } Context 'Start-Website throws an error' { + Mock -CommandName Get-Website -MockWith {return $MockWebsite} Mock -CommandName Set-ItemProperty Mock -CommandName Add-WebConfiguration @@ -1274,22 +1400,21 @@ try Mock -CommandName Confirm-UniqueServiceAutoStartProviders -MockWith {return $true} Mock -CommandName Start-Website -MockWith {throw} - It 'should throw the correct error' { - $ErrorId = 'WebsiteStateFailure' + It 'Should throw the correct error' { + $ErrorId = 'WebsiteStateFailure' $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation - $ErrorMessage = $LocalizedData.ErrorWebsiteStateFailure -f $MockParameters.Name, 'ScriptHalted' - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + $ErrorMessage = $LocalizedData.ErrorWebsiteStateFailure -f $MockParameters.Name, 'ScriptHalted' + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null { Set-TargetResource @MockParameters } | Should Throw $ErrorRecord } } Context 'All properties need to be updated and website must be stopped' { + $MockParameters = $MockParameters.Clone() $MockParameters.State = 'Stopped' @@ -1308,21 +1433,25 @@ try Mock -CommandName Update-DefaultPage - Mock -CommandName Set-Authentication + Mock -CommandName Get-Item -MockWith { return $MockWebsiteGetItem } + + Mock -CommandName Set-Item + + Mock -ModuleName $DSCHelperModuleName -CommandName Set-Authentication Mock -CommandName Stop-Website - Mock -CommandName Test-AuthenticationEnabled { return $true } ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $true } ` -ParameterFilter { ($Type -eq 'Anonymous') } - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } - - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $false } ` + -ParameterFilter { ($Type -in @('Basic','Digest','Windows')) } - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Windows') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Get-WebConfiguration ` + -ParameterFilter { $filter -eq '/system.applicationHost/serviceAutoStartProviders' } Set-TargetResource @MockParameters @@ -1332,12 +1461,13 @@ try Assert-MockCalled -CommandName Test-WebsiteBinding -Exactly 1 Assert-MockCalled -CommandName Update-WebsiteBinding -Exactly 1 Assert-MockCalled -CommandName Update-DefaultPage -Exactly 1 - Assert-MockCalled -CommandName Set-Authentication -Exactly 4 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Set-Authentication -Exactly 4 Assert-MockCalled -CommandName Stop-Website -Exactly 1 } } Context 'Website does not exist' { + $MockWebsite = @{ Name = 'MockName' PhysicalPath = 'C:\NonExistent' @@ -1385,21 +1515,17 @@ try Mock -CommandName Confirm-UniqueServiceAutoStartProviders -MockWith { return $false } - Mock -CommandName Set-Authentication + Mock -ModuleName $DSCHelperModuleName -CommandName Set-Authentication Mock -CommandName Start-Website - Mock -CommandName Test-AuthenticationEnabled { return $true } ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $true } ` -ParameterFilter { ($Type -eq 'Anonymous') } - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } - - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } - - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Windows') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest', 'Windows')) } Set-TargetResource @MockParameters @@ -1415,12 +1541,13 @@ try Assert-MockCalled -CommandName Update-DefaultPage -Exactly 1 Assert-MockCalled -CommandName Confirm-UniqueBinding -Exactly 1 Assert-MockCalled -CommandName Confirm-UniqueServiceAutoStartProviders -Exactly 1 - Assert-MockCalled -CommandName Set-Authentication -Exactly 4 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Set-Authentication -Exactly 4 Assert-MockCalled -CommandName Start-Website -Exactly 1 } } Context 'Website has unchanged logging directory' { + $MockWebsite = @{ Name = 'MockName' PhysicalPath = 'C:\NonExistent' @@ -1471,37 +1598,36 @@ try Mock -CommandName Confirm-UniqueServiceAutoStartProviders -MockWith { return $false } - Mock -CommandName Set-Authentication + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Set-Authentication - Mock -CommandName Test-AuthenticationEnabled { return $true } ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $true } ` -ParameterFilter { ($Type -eq 'Anonymous') } - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } - - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } - - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Windows') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest', 'Windows')) } Set-TargetResource @MockParameters It 'Should call all the mocks' { - Assert-MockCalled -CommandName Test-WebsiteBinding -Exactly 1 - Assert-MockCalled -CommandName Update-WebsiteBinding -Exactly 1 - Assert-MockCalled -CommandName Get-Item -Exactly 2 - Assert-MockCalled -CommandName Set-Item -Exactly 2 - Assert-MockCalled -CommandName Set-ItemProperty -Exactly 6 - Assert-MockCalled -CommandName Set-ItemProperty -ParameterFilter { $Name -eq 'LogFile.directory' } -Exactly 0 - Assert-MockCalled -CommandName Add-WebConfiguration -Exactly 1 - Assert-MockCalled -CommandName Update-DefaultPage -Exactly 1 - Assert-MockCalled -CommandName Confirm-UniqueServiceAutoStartProviders -Exactly 1 - Assert-MockCalled -CommandName Set-Authentication -Exactly 4 + Assert-MockCalled -CommandName Test-WebsiteBinding -Exactly 1 + Assert-MockCalled -CommandName Update-WebsiteBinding -Exactly 1 + Assert-MockCalled -CommandName Get-Item -Exactly 2 + Assert-MockCalled -CommandName Set-Item -Exactly 2 + Assert-MockCalled -CommandName Set-ItemProperty -Exactly 6 + Assert-MockCalled -CommandName Set-ItemProperty -ParameterFilter { $Name -eq 'LogFile.directory' } -Exactly 0 + Assert-MockCalled -CommandName Add-WebConfiguration -Exactly 1 + Assert-MockCalled -CommandName Update-DefaultPage -Exactly 1 + Assert-MockCalled -CommandName Confirm-UniqueServiceAutoStartProviders -Exactly 1 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Test-AuthenticationEnabled -Exactly 4 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Set-Authentication -Exactly 4 } } Context 'Website has changed logging directory' { + $MockWebsite = @{ Name = 'MockName' PhysicalPath = 'C:\NonExistent' @@ -1552,19 +1678,15 @@ try Mock -CommandName Confirm-UniqueServiceAutoStartProviders -MockWith { return $false } - Mock -CommandName Set-Authentication + Mock -ModuleName $DSCHelperModuleName -CommandName Set-Authentication - Mock -CommandName Test-AuthenticationEnabled { return $true } ` + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $true } ` -ParameterFilter { ($Type -eq 'Anonymous') } - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Basic') } - - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Digest') } - - Mock -CommandName Test-AuthenticationEnabled { return $false } ` - -ParameterFilter { ($Type -eq 'Windows') } + Mock -ModuleName $DSCHelperModuleName ` + -CommandName Test-AuthenticationEnabled { return $false } ` + -ParameterFilter { ($Type -in @('Basic', 'Digest', 'Windows')) } Set-TargetResource @MockParameters @@ -1578,11 +1700,12 @@ try Assert-MockCalled -CommandName Add-WebConfiguration -Exactly 1 Assert-MockCalled -CommandName Update-DefaultPage -Exactly 1 Assert-MockCalled -CommandName Confirm-UniqueServiceAutoStartProviders -Exactly 1 - Assert-MockCalled -CommandName Set-Authentication -Exactly 4 + Assert-MockCalled -ModuleName $DSCHelperModuleName -CommandName Set-Authentication -Exactly 4 } } Context 'New website cannot be started due to a binding conflict' { + $MockWebsite = @{ Name = 'MockName' PhysicalPath = 'C:\NonExistent' @@ -1626,21 +1749,21 @@ try Mock -CommandName Start-Website - It 'Should throw the correct error' { - $ErrorId = 'WebsiteBindingConflictOnStart' + $ErrorId = 'WebsiteBindingConflictOnStart' $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult - $ErrorMessage = $LocalizedData.ErrorWebsiteBindingConflictOnStart -f $MockParameters.Name - $Exception = New-Object -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + $ErrorMessage = $LocalizedData.ErrorWebsiteBindingConflictOnStart -f $MockParameters.Name + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null { Set-TargetResource @MockParameters } | Should Throw $ErrorRecord } } Context 'New-Website throws an error' { + Mock -CommandName Get-Website Mock -CommandName Get-Command -MockWith { @@ -1653,22 +1776,21 @@ try Mock -CommandName New-Website -MockWith {throw} - It 'should throw the correct error' { - $ErrorId = 'WebsiteCreationFailure' + It 'Should throw the correct error' { + $ErrorId = 'WebsiteCreationFailure' $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation - $ErrorMessage = $LocalizedData.ErrorWebsiteCreationFailure -f $MockParameters.Name, 'ScriptHalted' - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + $ErrorMessage = $LocalizedData.ErrorWebsiteCreationFailure -f $MockParameters.Name, 'ScriptHalted' + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null { Set-TargetResource @MockParameters } | Should Throw $ErrorRecord } } Context 'LogTruncateSize is larger in string comparison' { + $MockLogOutput = @{ directory = $MockParameters.LogPath logExtFileFlags = $MockParameters.LogFlags @@ -1685,10 +1807,13 @@ try Mock -CommandName Set-ItemProperty -MockWith { } + Mock -CommandName Test-AuthenticationInfo { return $true } + Mock -CommandName Get-WebConfigurationProperty ` -MockWith { return $MockLogOutput.logExtFileFlags } - Set-TargetResource -Ensure $MockParameters.Ensure ` + Set-TargetResource ` + -Ensure $MockParameters.Ensure ` -Name $MockParameters.Name ` -PhysicalPath $MockParameters.PhysicalPath ` -LogTruncateSize '5000000' ` @@ -1702,7 +1827,8 @@ try } } - Describe "how $script:DSCResourceName\Set-TargetResource responds to Ensure = 'Absent'" { + Describe "how $DSCResourceName\Set-TargetResource responds to Ensure = 'Absent'" { + $MockParameters = @{ Ensure = 'Absent' Name = 'MockName' @@ -1725,2127 +1851,139 @@ try It 'Should throw the correct error' { Mock -CommandName Remove-Website -MockWith {throw} - $ErrorId = 'WebsiteRemovalFailure' + $ErrorId = 'WebsiteRemovalFailure' $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation - $ErrorMessage = $LocalizedData.ErrorWebsiteRemovalFailure -f $MockParameters.Name, 'ScriptHalted' - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null + $ErrorMessage = $LocalizedData.ErrorWebsiteRemovalFailure -f $MockParameters.Name, 'ScriptHalted' + $Exception = New-Object -TypeName System.InvalidOperationException ` + -ArgumentList $ErrorMessage + $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null {Set-TargetResource @MockParameters} | Should Throw $ErrorRecord } } - Describe "$script:DSCResourceName\Confirm-UniqueBinding" { - Context 'Returns false when LogFlags are incorrect' { - - $MockLogOutput = @{ - logExtFileFlags = 'Date','Time','ClientIP','UserName','ServerIP','Method','UriStem','UriQuery','HttpStatus','Win32Status','TimeTaken','ServerPort','UserAgent','Referer','HttpSubStatus' - } - - $MockWebsite = @{ - Name = 'MockName' - LogFile = $MockLogOutput - } - - Mock -CommandName Get-WebSite ` - -MockWith { return $MockWebsite } - - $result = Compare-LogFlags -Name 'MockWebsite' -LogFlags 'Date','Time','ClientIP','UserName','ServerIP' - - It 'Should return false' { - $result | Should be $false - } + Describe "$DSCResourceName\Update-DefaultPage" { + $MockWebsite = @{ + Name = 'MockName' + DefaultPage = 'index.htm' } - Context 'Returns true when LogFlags are correct' { - - $MockLogOutput = @{ - logExtFileFlags = 'Date','Time','ClientIP','UserName','ServerIP' - } + Context 'Does not find the default page' { - $MockWebsite = @{ - Name = 'MockName' - LogFile = $MockLogOutput + Mock -CommandName Get-WebConfiguration -MockWith { + return @{value = 'index2.htm'} } - Mock -CommandName Get-WebSite ` - -MockWith { return $MockWebsite } + Mock -CommandName Add-WebConfiguration - $result = Compare-LogFlags -Name $MockWebsite.Name -LogFlags 'Date','Time','ClientIP','UserName','ServerIP' + It 'Should call Add-WebConfiguration' { + Update-DefaultPage -Name $MockWebsite.Name -DefaultPage $MockWebsite.DefaultPage - It 'Should return true' { - $result | Should be $true + Assert-MockCalled -CommandName Add-WebConfiguration } - } - } - Describe "$script:DSCResourceName\Confirm-UniqueBinding" { - $MockParameters = @{ - Name = 'MockSite' - } - - Context 'Website does not exist' { - Mock -CommandName Get-Website - It 'should throw the correct error' { - $ErrorId = 'WebsiteNotFound' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult - $ErrorMessage = $LocalizedData.ErrorWebsiteNotFound -f $MockParameters.Name - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - { Confirm-UniqueBinding -Name $MockParameters.Name } | Should Throw $ErrorRecord - } - } - - Context 'Expected behavior' { - $GetWebsiteOutput = @( - @{ - Name = $MockParameters.Name - State = 'Stopped' - Bindings = @{ - Collection = @( - @{ protocol = 'http'; bindingInformation = '*:80:' } - ) - } - } - ) - - Mock -CommandName Get-Website -MockWith { return $GetWebsiteOutput } - - It 'should not throw an error' { - { Confirm-UniqueBinding -Name $MockParameters.Name } | Should Not Throw - } - - It 'should call Get-Website twice' { - Assert-MockCalled -CommandName Get-Website -Exactly 2 - } - } - - Context 'Bindings are unique' { - $GetWebsiteOutput = @( - @{ - Name = $MockParameters.Name - State = 'Stopped' - Bindings = @{ - Collection = @( - @{ protocol = 'http'; bindingInformation = '*:80:' } - @{ protocol = 'http'; bindingInformation = '*:8080:' } - ) - } - } - @{ - Name = 'MockSite2' - State = 'Stopped' - Bindings = @{ - Collection = @( - @{ protocol = 'http'; bindingInformation = '*:81:' } - ) - } - } - @{ - Name = 'MockSite3' - State = 'Started' - Bindings = @{ - Collection = @( - @{ protocol = 'http'; bindingInformation = '*:8081:' } - ) - } - } - ) - - Mock -CommandName Get-Website -MockWith {return $GetWebsiteOutput} - - It 'should return True' { - Confirm-UniqueBinding -Name $MockParameters.Name | Should Be $true - } - } - - Context 'Bindings are not unique' { - $GetWebsiteOutput = @( - @{ - Name = $MockParameters.Name - State = 'Stopped' - Bindings = @{ - Collection = @( - @{ protocol = 'http'; bindingInformation = '*:80:' } - @{ protocol = 'http'; bindingInformation = '*:8080:' } - ) - } - } - @{ - Name = 'MockSite2' - State = 'Started' - Bindings = @{ - Collection = @( - @{ protocol = 'http'; bindingInformation = '*:80:' } - ) - } - } - @{ - Name = 'MockSite3' - State = 'Started' - Bindings = @{ - Collection = @( - @{ protocol = 'http'; bindingInformation = '*:8080:' } - ) - } - } - ) - - Mock -CommandName Get-Website -MockWith {return $GetWebsiteOutput} - - It 'should return False' { - Confirm-UniqueBinding -Name $MockParameters.Name | Should Be $false - } - } - - Context 'One of the bindings is assigned to another website that is Stopped' { - $GetWebsiteOutput = @( - @{ - Name = $MockParameters.Name - State = 'Stopped' - Bindings = @{ - Collection = @( - @{ protocol = 'http'; bindingInformation = '*:80:' } - @{ protocol = 'http'; bindingInformation = '*:8080:' } - ) - } - } - @{ - Name = 'MockSite2' - State = 'Stopped' - Bindings = @{ - Collection = @( - @{ protocol = 'http'; bindingInformation = '*:80:' } - ) - } - } - ) + Describe "$DSCResourceName\Test-LogCustomField"{ - Mock -CommandName Get-Website -MockWith { return $GetWebsiteOutput } - - It 'should return True if stopped websites are excluded' { - Confirm-UniqueBinding -Name $MockParameters.Name -ExcludeStopped | Should Be $true - } - - It 'should return False if stopped websites are not excluded' { - Confirm-UniqueBinding -Name $MockParameters.Name | Should Be $false - } - } + $MockWebsiteName = 'ContosoSite' - Context 'One of the bindings is assigned to another website that is Started' { - $GetWebsiteOutput = @( - @{ - Name = $MockParameters.Name - State = 'Stopped' - Bindings = @{ - Collection = @( - @{ protocol = 'http'; bindingInformation = '*:80:' } - @{ protocol = 'http'; bindingInformation = '*:8080:' } - ) - } - } - @{ - Name = 'MockSite2' - State = 'Stopped' - Bindings = @{ - Collection = @( - @{ protocol = 'http'; bindingInformation = '*:80:' } - ) - } - } - @{ - Name = 'MockSite3' - State = 'Started' - Bindings = @{ - Collection = @( - @{ protocol = 'http'; bindingInformation = '*:80:' } - ) - } - } - ) + $MockCimLogCustomFields = @( + New-CimInstance -ClassName MSFT_xLogCustomFieldInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + LogFieldName = 'ClientEncoding' + SourceName = 'Accept-Encoding' + SourceType = 'RequestHeader' + } + ) - Mock -CommandName Get-Website -MockWith { return $GetWebsiteOutput } + Context 'LogCustomField in desired state'{ - It 'should return False' { - Confirm-UniqueBinding -Name $MockParameters.Name -ExcludeStopped | Should Be $false + $MockDesiredLogCustomFields = @{ + LogFieldName = 'ClientEncoding' + SourceName = 'Accept-Encoding' + SourceType = 'RequestHeader' } - } - } - - Describe "$script:DSCResourceName\Confirm-UniqueServiceAutoStartProviders" { - $MockParameters = @{ - Name = 'MockServiceAutoStartProvider' - Type = 'MockApplicationType' - } - - Context 'Expected behavior' { - $MockWebConfiguration = @( - @{ - SectionPath = 'MockSectionPath' - PSPath = 'MockPSPath' - Collection = @( - [PSCustomObject] @{ - Name = 'MockServiceAutoStartProvider'; - Type = 'MockApplicationType' - } - ) - } - ) - - Mock -CommandName Get-WebConfiguration -MockWith {return $MockWebConfiguration} - It 'should not throw an error' { - { Confirm-UniqueServiceAutoStartProviders ` - -ServiceAutoStartProvider $MockParameters.Name ` - -ApplicationType $MockParameters.Type } | Should Not Throw - } + Mock -CommandName Get-WebConfigurationProperty ` + -MockWith { return $MockDesiredLogCustomFields } - It 'should call Get-WebConfiguration once' { - Assert-MockCalled -CommandName Get-WebConfiguration -Exactly 1 + It 'Should return True' { + Test-LogCustomField -Site $MockWebsiteName ` + -LogCustomField $MockCimLogCustomFields | Should Be $True } } - Context 'Conflicting Global Property' { - $MockWebConfiguration = @( - @{ - SectionPath = 'MockSectionPath' - PSPath = 'MockPSPath' - Collection = @( - [PSCustomObject] @{ - Name = 'MockServiceAutoStartProvider'; - Type = 'MockApplicationType' - } - ) - } - ) - - Mock -CommandName Get-WebConfiguration -MockWith { return $MockWebConfiguration } + Context 'LogCustomField not in desired state'{ - It 'should return Throw' { - $ErrorId = 'ServiceAutoStartProviderFailure' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation - $ErrorMessage = $LocalizedData.ErrorWebsiteTestAutoStartProviderFailure, 'ScriptHalted' - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - { Confirm-UniqueServiceAutoStartProviders ` - -ServiceAutoStartProvider $MockParameters.Name ` - -ApplicationType 'MockApplicationType2'} | Should Throw $ErrorRecord + $MockWrongLogCustomFields = @{ + LogFieldName = 'ClientEncoding' + SourceName = 'WrongSourceName' + SourceType = 'WrongSourceType' } - } - - Context 'ServiceAutoStartProvider does not exist' { - $MockWebConfiguration = @( - @{ - Name = '' - Type = '' - } - ) - Mock -CommandName Get-WebConfiguration -MockWith { return $MockWebConfiguration } + Mock -CommandName Get-WebConfigurationProperty ` + -MockWith { return $MockWrongLogCustomFields } - It 'should return False' { - Confirm-UniqueServiceAutoStartProviders ` - -ServiceAutoStartProvider $MockParameters.Name ` - -ApplicationType $MockParameters.Type | Should Be $false + It 'Should return False' { + Test-LogCustomField -Site $MockWebsiteName ` + -LogCustomField $MockCimLogCustomFields | Should Be $False } } - Context 'ServiceAutoStartProvider does exist' { - $MockWebConfiguration = @( - @{ - SectionPath = 'MockSectionPath' - PSPath = 'MockPSPath' - Collection = @( - [PSCustomObject] @{ - Name = 'MockServiceAutoStartProvider' ; - Type = 'MockApplicationType' - } - ) - } - ) + Context 'LogCustomField not present'{ - Mock -CommandName Get-WebConfiguration -MockWith { return $MockWebConfiguration } + Mock -CommandName Get-WebConfigurationProperty ` + -MockWith { return $false } - It 'should return True' { - Confirm-UniqueServiceAutoStartProviders ` - -ServiceAutoStartProvider $MockParameters.Name ` - -ApplicationType $MockParameters.Type | Should Be $true + It 'Should return False' { + Test-LogCustomField -Site $MockWebsiteName ` + -LogCustomField $MockCimLogCustomFields | Should Be $False } } } - Describe "$script:DSCResourceName\ConvertTo-CimBinding" { - Context 'IPv4 address is passed and the protocol is http' { - $MockWebBinding = @{ - bindingInformation = '127.0.0.1:80:MockHostName' - protocol = 'http' - } - - $Result = ConvertTo-CimBinding -InputObject $MockWebBinding - - It 'should return the IPv4 Address' { - $Result.IPAddress | Should Be '127.0.0.1' - } - - It 'should return the Protocol' { - $Result.Protocol | Should Be 'http' - } - - It 'should return the HostName' { - $Result.HostName | Should Be 'MockHostName' - } - - It 'should return the Port' { - $Result.Port | Should Be '80' - } - } + Describe "$DSCResourceName\Set-LogCustomField"{ - Context 'IPv6 address is passed and the protocol is http' { - $MockWebBinding = @{ - bindingInformation = '[0:0:0:0:0:0:0:1]:80:MockHostName' - protocol = 'http' - } + $MockWebsiteName = 'ContosoSite' - $Result = ConvertTo-CimBinding -InputObject $MockWebBinding + $MockCimLogCustomFields = @( + New-CimInstance -ClassName MSFT_xLogCustomFieldInformation ` + -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' ` + -ClientOnly ` + -Property @{ + LogFieldName = 'ClientEncoding' + SourceName = 'Accept-Encoding' + SourceType = 'RequestHeader' + } + ) - It 'should return the IPv6 Address' { - $Result.IPAddress | Should Be '0:0:0:0:0:0:0:1' - } + Context 'Create new LogCustomField'{ - It 'should return the Protocol' { - $Result.Protocol | Should Be 'http' - } + Mock -CommandName Set-WebConfigurationProperty - It 'should return the HostName' { - $Result.HostName | Should Be 'MockHostName' + It 'Should not throw an error' { + { Set-LogCustomField -Site $MockWebsiteName -LogCustomField $MockCimLogCustomFields } | Should Not Throw } - It 'should return the Port' { - $Result.Port | Should Be '80' + It 'Should call should call expected mocks' { + Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 2 } } - Context 'IPv4 address with SSL certificate is passed' { - $MockWebBinding = @{ - bindingInformation = '127.0.0.1:443:MockHostName' - protocol = 'https' - certificateHash = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - certificateStoreName = 'MY' - sslFlags = '1' - } - - $Result = ConvertTo-CimBinding -InputObject $MockWebBinding - - It 'should return the IPv4 Address' { - $Result.IPAddress | Should Be '127.0.0.1' - } - - It 'should return the Protocol' { - $Result.Protocol | Should Be 'https' - } - - It 'should return the HostName' { - $Result.HostName | Should Be 'MockHostName' - } - - It 'should return the Port' { - $Result.Port | Should Be '443' - } + Context 'Modify existing LogCustomField'{ - It 'should return the CertificateThumbprint' { - $Result.CertificateThumbprint | Should Be '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - } - - It 'should return the CertificateStoreName' { - $Result.CertificateStoreName | Should Be 'MY' - } - - It 'should return the SslFlags' { - $Result.SslFlags | Should Be '1' - } - } - - Context 'IPv6 address with SSL certificate is passed' { - $MockWebBinding = @{ - bindingInformation = '[0:0:0:0:0:0:0:1]:443:MockHostName' - protocol = 'https' - certificateHash = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - certificateStoreName = 'MY' - sslFlags = '1' - } - - $Result = ConvertTo-CimBinding -InputObject $MockWebBinding - - It 'should return the IPv6 Address' { - $Result.IPAddress | Should Be '0:0:0:0:0:0:0:1' - } - - It 'should return the Protocol' { - $Result.Protocol | Should Be 'https' - } - - It 'should return the HostName' { - $Result.HostName | Should Be 'MockHostName' - } - - It 'should return the Port' { - $Result.Port | Should Be '443' - } - - It 'should return the CertificateThumbprint' { - $Result.CertificateThumbprint | Should Be '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - } - - It 'should return the CertificateStoreName' { - $Result.CertificateStoreName | Should Be 'MY' - } - - It 'should return the SslFlags' { - $Result.SslFlags | Should Be '1' - } - } - } - - Describe "$script:DSCResourceName\ConvertTo-WebBinding" -Tag 'ConvertTo' { - Context 'Expected behaviour' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'https' - BindingInformation = 'NonsenseString' - IPAddress = '*' - Port = 443 - HostName = 'web01.contoso.com' - CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - CertificateStoreName = 'WebHosting' - SslFlags = 1 - } -ClientOnly - ) - - $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo - - It 'should return the correct Protocol value' { - $Result.protocol | Should Be 'https' - } - - It 'should return the correct BindingInformation value' { - $Result.bindingInformation | Should Be '*:443:web01.contoso.com' - } - - It 'should return the correct CertificateHash value' { - $Result.certificateHash | Should Be 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - } - - It 'should return the correct CertificateStoreName value' { - $Result.certificateStoreName | Should Be 'WebHosting' - } - - It 'should return the correct SslFlags value' { - $Result.sslFlags | Should Be 1 - } - } - - Context 'IP address is invalid' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'http' - IPAddress = '127.0.0.256' - } -ClientOnly - ) - - It 'should throw the correct error' { - $ErrorId = 'WebBindingInvalidIPAddress' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument - $ErrorMessage = $LocalizedData.ErrorWebBindingInvalidIPAddress -f $MockBindingInfo.IPAddress, 'Exception calling "Parse" with "1" argument(s): "An invalid IP address was specified."' - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Throw $ErrorRecord - } - } - - Context 'Port is not specified' { - It 'should set the default HTTP port' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'http' - } - ) - - $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo - $Result.bindingInformation | Should Be '*:80:' - } - - It 'should set the default HTTPS port' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'https' - CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - } - ) - - $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo - $Result.bindingInformation | Should Be '*:443:' - } - } - - Context 'Port is invalid' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'http' - Port = 0 - } -ClientOnly - ) - - It 'should throw the correct error' { - $ErrorId = 'WebBindingInvalidPort' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument - $ErrorMessage = $LocalizedData.ErrorWebBindingInvalidPort -f $MockBindingInfo.Port - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - {ConvertTo-WebBinding -InputObject $MockBindingInfo} | Should Throw $ErrorRecord - } - } - - Context 'Protocol is HTTPS and CertificateThumbprint contains the Left-to-Right Mark character' { - $MockThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - - $AsciiEncoding = [System.Text.Encoding]::ASCII - $UnicodeEncoding = [System.Text.Encoding]::Unicode - - $AsciiBytes = $AsciiEncoding.GetBytes($MockThumbprint) - $UnicodeBytes = [System.Text.Encoding]::Convert($AsciiEncoding, $UnicodeEncoding, $AsciiBytes) - $LrmCharBytes = $UnicodeEncoding.GetBytes([Char]0x200E) - - # Prepend the Left-to-Right Mark character to CertificateThumbprint - $MockThumbprintWithLrmChar = $UnicodeEncoding.GetString(($LrmCharBytes + $UnicodeBytes)) - - $MockBindingInfo = @( - New-CimInstance -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'https' - CertificateThumbprint = $MockThumbprintWithLrmChar - CertificateStoreName = 'MY' - } -ClientOnly - ) - - It 'Input - CertificateThumbprint should contain the Left-to-Right Mark character' { - $MockBindingInfo[0].CertificateThumbprint -match '^\u200E' | Should Be $true - } - - It 'Output - certificateHash should not contain the Left-to-Right Mark character' { - $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo - $Result.certificateHash -match '^\u200E' | Should Be $false - } - } - - Context 'Protocol is HTTPS and CertificateThumbprint is not specified' { - $MockBindingInfo = @( - New-CimInstance -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'https' - CertificateThumbprint = '' - } -ClientOnly - ) - - It 'should throw the correct error' { - $ErrorId = 'WebBindingMissingCertificateThumbprint' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument - $ErrorMessage = $LocalizedData.ErrorWebBindingMissingCertificateThumbprint -f $MockBindingInfo.Protocol - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Throw $ErrorRecord - } - } - - Context 'Protocol is HTTPS and CertificateSubject is specified' { - $MockBindingInfo = @( - New-CimInstance -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'https' - CertificateSubject = 'TestCertificate' - } -ClientOnly - ) - - Mock Find-Certificate -MockWith { - return [PSCustomObject]@{ - Thumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - } - } - - It 'should not throw an error' { - { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Not Throw - } - It 'should return the correct thumbprint' { - $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo - $Result.certificateHash | Should Be 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - } - It 'Should call Find-Certificate mock' { - Assert-MockCalled -CommandName Find-Certificate -Times 1 - } - } - - Context 'Protocol is HTTPS and full CN of CertificateSubject is specified' { - $MockBindingInfo = @( - New-CimInstance -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'https' - CertificateSubject = 'CN=TestCertificate' - } -ClientOnly - ) - - Mock Find-Certificate -MockWith { - return [PSCustomObject]@{ - Thumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - } - } - - It 'should not throw an error' { - { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Not Throw - } - It 'should return the correct thumbprint' { - $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo - $Result.certificateHash | Should Be 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - } - It 'Should call Find-Certificate mock' { - Assert-MockCalled -CommandName Find-Certificate -Times 1 - } - } - - Context 'Protocol is HTTPS and invalid CertificateSubject is specified' { - $MockBindingInfo = @( - New-CimInstance -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'https' - CertificateSubject = 'TestCertificate' - CertificateStoreName = 'MY' - } -ClientOnly - ) - - Mock Find-Certificate - - It 'should throw the correct error' { - $CertificateSubject = "CN=$($MockBindingInfo.CertificateSubject)" - $ErrorId = 'WebBindingInvalidCertificateSubject' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument - $ErrorMessage = $LocalizedData.ErrorWebBindingInvalidCertificateSubject -f $CertificateSubject, $MockBindingInfo.CertificateStoreName - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Throw $ErrorRecord - } - } - - Context 'Protocol is HTTPS and CertificateStoreName is not specified' { - $MockBindingInfo = @( - New-CimInstance -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'https' - CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - CertificateStoreName = '' - } -ClientOnly - ) - - It 'should set CertificateStoreName to the default value' { - $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo - $Result.certificateStoreName | Should Be 'MY' - } - } - - Context 'Protocol is HTTPS and HostName is not specified for use with Server Name Indication' { - $MockBindingInfo = @( - New-CimInstance -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'https' - IPAddress = '*' - Port = 443 - HostName = '' - CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - CertificateStoreName = 'WebHosting' - SslFlags = 1 - } -ClientOnly - ) - - It 'should throw the correct error' { - $ErrorId = 'WebBindingMissingSniHostName' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument - $ErrorMessage = $LocalizedData.ErrorWebBindingMissingSniHostName - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Throw $ErrorRecord - } - } - - Context 'Protocol is not HTTPS' { - $MockBindingInfo = @( - New-CimInstance -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'http' - CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - CertificateStoreName = 'WebHosting' - SslFlags = 1 - } -ClientOnly - ) - - It 'should ignore SSL properties' { - $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo - $Result.certificateHash | Should Be '' - $Result.certificateStoreName | Should Be '' - $Result.sslFlags | Should Be 0 - } - } - - Context 'Protocol is neither HTTP nor HTTPS' { - It 'should throw an error if BindingInformation is not specified' { - $MockBindingInfo = @( - New-CimInstance -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'net.tcp' - BindingInformation = '' - } -ClientOnly - ) - - $ErrorId = 'WebBindingMissingBindingInformation' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument - $ErrorMessage = $LocalizedData.ErrorWebBindingMissingBindingInformation -f $MockBindingInfo.Protocol - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - { ConvertTo-WebBinding -InputObject $MockBindingInfo } | Should Throw $ErrorRecord - } - - It 'should use BindingInformation and ignore IPAddress, Port, and HostName' { - $MockBindingInfo = @( - New-CimInstance -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - Protocol = 'net.tcp' - BindingInformation = '808:*' - IPAddress = '127.0.0.1' - Port = 80 - HostName = 'web01.contoso.com' - } -ClientOnly - ) - - $Result = ConvertTo-WebBinding -InputObject $MockBindingInfo - $Result.BindingInformation | Should Be '808:*' - } - } - } - - Describe "$script:DSCResourceName\Format-IPAddressString" { - Context 'Input value is not valid' { - It 'should throw an error' { - { Format-IPAddressString -InputString 'Invalid' } | Should Throw - } - } - - Context 'Input value is valid' { - It 'should return "*" when input value is null' { - Format-IPAddressString -InputString $null | Should Be '*' - } - - It 'should return "*" when input value is empty' { - Format-IPAddressString -InputString '' | Should Be '*' - } - - It 'should return normalized IPv4 address' { - Format-IPAddressString -InputString '192.10' | Should Be '192.0.0.10' - } - - It 'should return normalized IPv6 address enclosed in square brackets' { - Format-IPAddressString ` - -InputString 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' | Should Be '[fe80::202:b3ff:fe1e:8329]' - } - } - } - - Describe "$script:DSCResourceName\Get-AuthenticationInfo" { - $MockWebsite = @{ - Name = 'MockName' - PhysicalPath = 'C:\NonExistent' - State = 'Started' - ApplicationPool = 'MockPool' - Bindings = @{Collection = @($MockWebBinding)} - EnabledProtocols = 'http' - ApplicationDefaults = @{Collection = @($MockPreloadAndAutostartProviders)} - Count = 1 - } - - Context 'Expected behavior' { - Mock -CommandName Get-WebConfigurationProperty -MockWith { return 'False'} - - It 'should not throw an error' { - { Get-AuthenticationInfo -site $MockWebsite.Name } | Should Not Throw - } - - It 'should call Get-WebConfigurationProperty four times' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 4 - } - } - - Context 'AuthenticationInfo is false' { - $MockWebConfiguration = @( - @{ - Value = $false - } - ) - - Mock -CommandName Get-WebConfigurationProperty -MockWith { $MockWebConfiguration } - - It 'should all be false' { - $result = Get-AuthenticationInfo -site $MockWebsite.Name - $result.Anonymous | Should be $false - $result.Digest | Should be $false - $result.Basic | Should be $false - $result.Windows | Should be $false - } - - It 'should call Get-WebConfigurationProperty four times' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 4 - } - } - - Context 'AuthenticationInfo is true' { - $MockWebConfiguration = @( - @{ - Value = 'True' - } - ) - - Mock -CommandName Get-WebConfigurationProperty -MockWith { $MockWebConfiguration } - - It 'should all be true' { - $result = Get-AuthenticationInfo -site $MockWebsite.Name - $result.Anonymous | Should be True - $result.Digest | Should be True - $result.Basic | Should be True - $result.Windows | Should be True - } - - It 'should call Get-WebConfigurationProperty four times' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 4 - } - } - } - - Describe "$script:DSCResourceName\Get-DefaultAuthenticationInfo" { - Context 'Expected behavior' { - It 'should not throw an error' { - { Get-DefaultAuthenticationInfo }| - Should Not Throw - } - } - - Context 'Get-DefaultAuthenticationInfo should produce a false CimInstance' { - It 'should all be false' { - $result = Get-DefaultAuthenticationInfo - $result.Anonymous | Should be False - $result.Digest | Should be False - $result.Basic | Should be False - $result.Windows | Should be False - } - } - } - - Describe "$script:DSCResourceName\Set-Authentication" { - Context 'Expected behavior' { - $MockWebsite = @{ - Name = 'MockName' - PhysicalPath = 'C:\NonExistent' - State = 'Started' - ApplicationPool = 'MockPool' - Bindings = @{Collection = @($MockWebBinding)} - EnabledProtocols = 'http' - ApplicationDefaults = @{Collection = @($MockPreloadAndAutostartProviders)} - Count = 1 - } - - Mock -CommandName Set-WebConfigurationProperty - - It 'should not throw an error' { - { Set-Authentication ` - -Site $MockWebsite.Name ` - -Type Basic ` - -Enabled $true } | Should Not Throw - } - - It 'should call Set-WebConfigurationProperty once' { - Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 1 - } - } - } - - Describe "$script:DSCResourceName\Set-AuthenticationInfo" { - Context 'Expected behavior' { - $MockWebsite = @{ - Name = 'MockName' - PhysicalPath = 'C:\NonExistent' - State = 'Started' - ApplicationPool = 'MockPool' - Bindings = @{Collection = @($MockWebBinding)} - EnabledProtocols = 'http' - ApplicationDefaults = @{Collection = @($MockPreloadAndAutostartProviders)} - Count = 1 - } - - Mock -CommandName Set-WebConfigurationProperty - - $AuthenticationInfo = New-CimInstance ` - -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly ` - -Property @{Anonymous='true';Basic='false';Digest='false';Windows='false'} - - It 'should not throw an error' { - { Set-AuthenticationInfo ` - -Site $MockWebsite.Name ` - -AuthenticationInfo $AuthenticationInfo } | Should Not Throw - } - - It 'should call should call expected mocks' { - Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 4 - } - } - } - - Describe "$script:DSCResourceName\Test-AuthenticationEnabled" { - $MockWebsite = @{ - Name = 'MockName' - PhysicalPath = 'C:\NonExistent' - State = 'Started' - ApplicationPool = 'MockPool' - Bindings = @{Collection = @($MockWebBinding)} - EnabledProtocols = 'http' - ApplicationDefaults = @{Collection = @($MockPreloadAndAutostartProviders)} - Count = 1 - } - - Context 'Expected behavior' { - $MockWebConfiguration = @( - @{ - Value = 'False' - } - ) - - Mock -CommandName Get-WebConfigurationProperty -MockWith {$MockWebConfiguration} - - It 'should not throw an error' { - { Test-AuthenticationEnabled ` - -Site $MockWebsite.Name ` - -Type 'Basic'} | Should Not Throw - } - - It 'should call expected mocks' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 1 - } - } - - Context 'AuthenticationInfo is false' { - $MockWebConfiguration = @( - @{ - Value = 'False' - } - ) - - Mock -CommandName Get-WebConfigurationProperty -MockWith { $MockWebConfiguration } - - It 'should return false' { - Test-AuthenticationEnabled -Site $MockWebsite.Name -Type 'Basic' | Should be False - } - - It 'should call expected mocks' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 1 - } - } - - Context 'AuthenticationInfo is true' { - $MockWebConfiguration = @( - @{ - Value = 'True' - } - ) - - Mock -CommandName Get-WebConfigurationProperty -MockWith { $MockWebConfiguration} - - It 'should all be true' { - Test-AuthenticationEnabled -Site $MockWebsite.Name -Type 'Basic' | Should be True - } - - It 'should call expected mocks' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 1 - } - } - } - - Describe "$script:DSCResourceName\Test-AuthenticationInfo" { - Mock -CommandName Get-WebConfigurationProperty -MockWith {$MockWebConfiguration} - - $MockWebsite = @{ - Name = 'MockName' - PhysicalPath = 'C:\NonExistent' - State = 'Started' - ApplicationPool = 'MockPool' - Bindings = @{Collection = @($MockWebBinding)} - EnabledProtocols = 'http' - ApplicationDefaults = @{Collection = @($MockPreloadAndAutostartProviders)} - Count = 1 - } - - $MockWebConfiguration = @( - @{ - Value = 'False' - } - ) - - $AuthenticationInfo = New-CimInstance ` - -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly ` - -Property @{ Anonymous='false'; Basic='true'; Digest='false'; Windows='false' } - - Context 'Expected behavior' { - It 'should not throw an error' { - { Test-AuthenticationInfo ` - -Site $MockWebsite.Name ` - -AuthenticationInfo $AuthenticationInfo } | Should Not Throw - } - - It 'should call expected mocks' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 2 - } - } - - Context 'Return False when AuthenticationInfo is not correct' { - Mock -CommandName Get-WebConfigurationProperty -MockWith { $MockWebConfiguration} - - It 'should return false' { - Test-AuthenticationInfo -Site $MockWebsite.Name -AuthenticationInfo $AuthenticationInfo | Should be False - } - - It 'should call expected mocks' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 2 - } - } - - Context 'Return True when AuthenticationInfo is correct' { - $MockWebConfiguration = @( - @{ - Value = 'True' - } - ) - - $AuthenticationInfo = New-CimInstance ` - -ClassName MSFT_xWebApplicationAuthenticationInformation ` - -ClientOnly ` - -Property @{ Anonymous='true'; Basic='true'; Digest='true'; Windows='true' } - - Mock -CommandName Get-WebConfigurationProperty -MockWith { $MockWebConfiguration} - - It 'should return true' { - Test-AuthenticationInfo ` - -Site $MockWebsite.Name ` - -AuthenticationInfo $AuthenticationInfo | Should be True - } - - It 'should call expected mocks' { - Assert-MockCalled -CommandName Get-WebConfigurationProperty -Exactly 4 - } - } - } - - Describe "$script:DSCResourceName\Test-BindingInfo" { - Context 'BindingInfo is valid' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'http' - IPAddress = '*' - Port = 80 - HostName = '' - CertificateThumbprint = '' - CertificateStoreName = '' - SslFlags = 0 - } - - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'https' - IPAddress = '*' - Port = 443 - HostName = 'web01.contoso.com' - CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - CertificateStoreName = 'WebHosting' - SslFlags = 1 - } - ) - - It 'should return True' { - Test-BindingInfo -BindingInfo $MockBindingInfo | Should Be $true - } - } - - Context 'BindingInfo contains multiple items with the same IPAddress, Port, and HostName combination' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'http' - IPAddress = '*' - Port = 8080 - HostName = 'web01.contoso.com' - CertificateThumbprint = '' - CertificateStoreName = '' - SslFlags = 0 - } - - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'http' - IPAddress = '*' - Port = 8080 - HostName = 'web01.contoso.com' - CertificateThumbprint = '' - CertificateStoreName = '' - SslFlags = 0 - } - ) - - It 'should return False' { - Test-BindingInfo -BindingInfo $MockBindingInfo | Should Be $false - } - } - - Context 'BindingInfo contains items that share the same Port but have different Protocols' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'http' - IPAddress = '127.0.0.1' - Port = 8080 - HostName = '' - CertificateThumbprint = '' - CertificateStoreName = '' - SslFlags = 0 - } - - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'https' - IPAddress = '*' - Port = 8080 - HostName = 'web01.contoso.com' - CertificateThumbprint = 'C65CE51E20C523DEDCE979B9922A0294602D9D5C' - CertificateStoreName = 'WebHosting' - SslFlags = 1 - } - ) - - It 'should return False' { - Test-BindingInfo -BindingInfo $MockBindingInfo | Should Be $false - } - } - - Context 'BindingInfo contains multiple items with the same Protocol and BindingInformation combination' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'net.tcp' - BindingInformation = '808:*' - } - - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'net.tcp' - BindingInformation = '808:*' - } - ) - - It 'should return False' { - Test-BindingInfo -BindingInfo $MockBindingInfo | Should Be $false - } - } - } - - Describe "$script:DSCResourceName\Test-PortNumber" { - Context 'Input value is not valid' { - It 'should not throw an error' { - {Test-PortNumber -InputString 'InvalidString'} | Should Not Throw - } - - It 'should return False' { - Test-PortNumber -InputString 'InvalidString' | Should Be $false - } - - It 'should return False when input value is null' { - Test-PortNumber -InputString $null | Should Be $false - } - - It 'should return False when input value is empty' { - Test-PortNumber -InputString '' | Should Be $false - } - - It 'should return False when input value is not between 1 and 65535' { - Test-PortNumber -InputString '100000' | Should Be $false - } - } - - Context 'Input value is valid' { - It 'should return True' { - Test-PortNumber -InputString '443' | Should Be $true - } - } - } - - Describe "$script:DSCResourceName\Test-WebsiteBinding" { - $MockWebBinding = @( - @{ - bindingInformation = '*:80:' - protocol = 'http' - certificateHash = '' - certificateStoreName = '' - sslFlags = '0' - } - ) - - $MockWebsite = @{ - Name = 'MockName' - Bindings = @{Collection = @($MockWebBinding)} - } - - Mock -CommandName Get-WebSite -MockWith {return $MockWebsite} - - Context 'Test-BindingInfo returns False' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'http' - IPAddress = '*' - Port = 80 - HostName = '' - } - ) - - It 'should throw the correct error' { - Mock -CommandName Test-BindingInfo -MockWith {return $false} - - $ErrorId = 'WebsiteBindingInputInvalidation' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult - $ErrorMessage = $LocalizedData.ErrorWebsiteBindingInputInvalidation -f $MockWebsite.Name - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - { Test-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo } | Should Throw $ErrorRecord - } - } - - Context 'Bindings comparison throws an error' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'http' - IPAddress = '*' - Port = 80 - HostName = '' - } - ) - - $ErrorId = 'WebsiteCompareFailure' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult - $ErrorMessage = $LocalizedData.ErrorWebsiteCompareFailure -f $MockWebsite.Name, 'ScriptHalted' - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - It 'should not return an error' { - { Test-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo} | Should Not Throw $ErrorRecord - } - } - - Context 'Port is different' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'http' - IPAddress = '*' - Port = 8080 - HostName = '' - CertificateThumbprint = '' - CertificateStoreName = '' - SslFlags = 0 - } - ) - - It 'should return False' { - Test-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo | Should Be $false - } - } - - Context 'Protocol is different' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'https' - IPAddress = '*' - Port = 80 - HostName = '' - CertificateThumbprint = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - CertificateStoreName = 'WebHosting' - SslFlags = 0 - } - ) - - It 'should return False' { - Test-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo | Should Be $false - } - } - - Context 'IPAddress is different' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'http' - IPAddress = '127.0.0.1' - Port = 80 - HostName = '' - CertificateThumbprint = '' - CertificateStoreName = '' - SslFlags = 0 - } - ) - - It 'should return False' { - Test-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo | Should Be $false - } - } - - Context 'HostName is different' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'http' - IPAddress = '*' - Port = 80 - HostName = 'MockHostName' - CertificateThumbprint = '' - CertificateStoreName = '' - SslFlags = 0 - } - ) - - It 'should return False' { - Test-WebsiteBinding -Name $MockWebsite.Name -BindingInfo $MockBindingInfo | - Should Be $false - } - } - - Context 'CertificateThumbprint is different' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'https' - IPAddress = '*' - Port = 443 - HostName = '' - CertificateThumbprint = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - CertificateStoreName = 'MY' - SslFlags = 0 - } - ) - - $MockWebBinding = @( - @{ - bindingInformation = '*:443:' - protocol = 'https' - certificateHash = 'B30F3184A831320382C61EFB0551766321FA88A5' - certificateStoreName = 'MY' - sslFlags = '0' - } - ) - - $MockWebsite = @{ - Name = 'MockSite' - Bindings = @{ Collection = @($MockWebBinding) } - } - - Mock -CommandName Get-WebSite -MockWith {return $MockWebsite} - - It 'should return False' { - Test-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo | Should Be $false - } - } - - Context 'CertificateStoreName is different' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'https' - IPAddress = '*' - Port = 443 - HostName = '' - CertificateThumbprint = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - CertificateStoreName = 'MY' - SslFlags = 0 - } - ) - - $MockWebBinding = @{ - bindingInformation = '*:443:' - protocol = 'https' - certificateHash = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - certificateStoreName = 'WebHosting' - sslFlags = '0' - } - - $MockWebsite = @{ - Name = 'MockSite' - Bindings = @{ Collection = @($MockWebBinding) } - } - - Mock -CommandName Get-WebSite -MockWith { return $MockWebsite } - - It 'should return False' { - Test-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo | Should Be $false - } - } - - Context 'CertificateStoreName is different and no CertificateThumbprint is specified' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'https' - IPAddress = '*' - Port = 443 - HostName = '' - CertificateThumbprint = '' - CertificateStoreName = 'MY' - SslFlags = 0 - } - ) - - $MockWebBinding = @{ - bindingInformation = '*:443:' - protocol = 'https' - certificateHash = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - certificateStoreName = 'WebHosting' - sslFlags = '0' - } - - $MockWebsite = @{ - Name = 'MockSite' - Bindings = @{Collection = @($MockWebBinding)} - } - - Mock -CommandName Get-WebSite -MockWith {return $MockWebsite} - - $ErrorId = 'WebsiteBindingInputInvalidation' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult - $ErrorMessage = $LocalizedData.ErrorWebsiteBindingInputInvalidation -f $MockWebsite.Name - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - It 'should throw the correct error' { - { Test-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo} | Should Throw $ErrorRecord - } - } - - Context 'SslFlags is different' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'https' - IPAddress = '*' - Port = 443 - HostName = 'web01.contoso.com' - CertificateThumbprint = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - CertificateStoreName = 'WebHosting' - SslFlags = 1 - } - ) - - $MockWebBinding = @{ - bindingInformation = '*:443:web01.contoso.com' - protocol = 'https' - certificateHash = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - certificateStoreName = 'WebHosting' - sslFlags = '0' - } - - $MockWebsite = @{ - Name = 'MockSite' - Bindings = @{ Collection = @($MockWebBinding) } - } - - Mock -CommandName Get-WebSite -MockWith {return $MockWebsite} - - It 'should return False' { - Test-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo | Should Be $false - } - } - - Context 'Bindings are identical' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'https' - Port = 443 - IPAddress = '*' - HostName = 'web01.contoso.com' - CertificateThumbprint = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - CertificateStoreName = 'WebHosting' - SslFlags = 1 - } - - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - IPAddress = '*' - Port = 8080 - HostName = '' - Protocol = 'http' - CertificateThumbprint = '' - CertificateStoreName = '' - SslFlags = 0 - } - ) - - $MockWebBinding = @( - @{ - bindingInformation = '*:443:web01.contoso.com' - protocol = 'https' - certificateHash = '1D3324C6E2F7ABC794C9CB6CA426B8D0F81045CD' - certificateStoreName = 'WebHosting' - sslFlags = '1' - } - @{ - bindingInformation = '*:8080:' - protocol = 'http' - certificateHash = '' - certificateStoreName = '' - sslFlags = '0' - } - ) - - $MockWebsite = @{ - Name = 'MockSite' - Bindings = @{Collection = @($MockWebBinding)} - } - - Mock -CommandName Get-WebSite -MockWith {return $MockWebsite} - - It 'should return True' { - Test-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo | Should Be $true - } - } - - Context 'Bindings are different' { - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'http' - IPAddress = '*' - Port = 80 - HostName = '' - CertificateThumbprint = '' - CertificateStoreName = '' - SslFlags = 0 - } - - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'http' - IPAddress = '*' - Port = 8080 - HostName = '' - CertificateThumbprint = '' - CertificateStoreName = '' - SslFlags = 0 - } - ) - - $MockWebBinding = @( - @{ - bindingInformation = '*:80:' - protocol = 'http' - certificateHash = '' - certificateStoreName = '' - sslFlags = '0' - } - - @{ - bindingInformation = '*:8081:' - protocol = 'http' - certificateHash = '' - certificateStoreName = '' - sslFlags = '0' - } - ) - - $MockWebsite = @{ - Name = 'MockSite' - Bindings = @{Collection = @($MockWebBinding)} - } - - Mock -CommandName Get-Website -MockWith {return $MockWebsite} - - It 'should return False' { - Test-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo | Should Be $false - } - } - } - - Describe "$script:DSCResourceName\Update-DefaultPage" { - $MockWebsite = @{ - Ensure = 'Present' - Name = 'MockName' - PhysicalPath = 'C:\NonExistent' - State = 'Started' - ApplicationPool = 'MockPool' - DefaultPage = 'index.htm' - } - - Context 'Does not find the default page' { - Mock -CommandName Get-WebConfiguration -MockWith { - return @{value = 'index2.htm'} - } - - Mock -CommandName Add-WebConfiguration - - It 'should call Add-WebConfiguration' { - $Result = Update-DefaultPage -Name $MockWebsite.Name -DefaultPage $MockWebsite.DefaultPage - Assert-MockCalled -CommandName Add-WebConfiguration - } - } - } - - Describe "$script:DSCResourceName\Update-WebsiteBinding" { - $MockWebsite = @{ - Name = 'MockSite' - ItemXPath = "/system.applicationHost/sites/site[@name='MockSite']" - } - - $MockBindingInfo = @( - New-CimInstance ` - -ClassName MSFT_xWebBindingInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -ClientOnly ` - -Property @{ - Protocol = 'https' - IPAddress = '*' - Port = 443 - HostName = '' - CertificateThumbprint = '5846A1B276328B1A32A30150858F6383C1F30E1F' - CertificateStoreName = 'MY' - SslFlags = 0 - } - ) - - Mock -CommandName Get-WebConfiguration -ParameterFilter { - $Filter -eq '/system.applicationHost/sites/site' - } -MockWith { return $MockWebsite } -Verifiable - - Mock -CommandName Clear-WebConfiguration -Verifiable - - Context 'Expected behavior' { - Mock -CommandName Add-WebConfiguration - Mock -CommandName Set-WebConfigurationProperty - - Mock -CommandName Get-WebConfiguration -ParameterFilter { - $Filter -eq "$($MockWebsite.ItemXPath)/bindings/binding[last()]" - } -MockWith { - New-Module -AsCustomObject -ScriptBlock { - function AddSslCertificate {} - } - } -Verifiable - - Update-WebsiteBinding -Name $MockWebsite.Name -BindingInfo $MockBindingInfo - - It 'Should call all the mocks' { - Assert-VerifiableMock - Assert-MockCalled -CommandName Add-WebConfiguration -Exactly $MockBindingInfo.Count - Assert-MockCalled -CommandName Set-WebConfigurationProperty - } - } - - Context 'Website does not exist' { - Mock -CommandName Get-WebConfiguration -ParameterFilter { - $Filter -eq '/system.applicationHost/sites/site' - } -MockWith { - return $null - } - - It 'should throw the correct error' { - $ErrorId = 'WebsiteNotFound' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult - $ErrorMessage = $LocalizedData.ErrorWebsiteNotFound -f $MockWebsite.Name - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - { Update-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo} | Should Throw $ErrorRecord - } - } - - Context 'Error on adding a new binding' { - Mock -CommandName Add-WebConfiguration -ParameterFilter { - $Filter -eq "$($MockWebsite.ItemXPath)/bindings" - } -MockWith { throw } - - It 'should throw the correct error' { - $ErrorId = 'WebsiteBindingUpdateFailure' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult - $ErrorMessage = $LocalizedData.ErrorWebsiteBindingUpdateFailure -f $MockWebsite.Name, 'ScriptHalted' - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - { Update-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo } | Should Throw $ErrorRecord - } - } - - Context 'Error on setting sslFlags attribute' { - Mock -CommandName Add-WebConfiguration - - Mock -CommandName Set-WebConfigurationProperty -ParameterFilter { - $Filter -eq "$($MockWebsite.ItemXPath)/bindings/binding[last()]" -and $Name -eq 'sslFlags' - } ` -MockWith { throw } - - It 'should throw the correct error' { - $ErrorId = 'WebsiteBindingUpdateFailure' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult - $ErrorMessage = $LocalizedData.ErrorWebsiteBindingUpdateFailure -f $MockWebsite.Name, 'ScriptHalted' - $Exception = New-Object ` - -TypeName System.InvalidOperationException ` - -ArgumentList $ErrorMessage - $ErrorRecord = New-Object ` - -TypeName System.Management.Automation.ErrorRecord ` - -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - { Update-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo } | Should Throw $ErrorRecord - } - } - - Context 'Error on adding SSL certificate' { - Mock -CommandName Add-WebConfiguration - - Mock -CommandName Set-WebConfigurationProperty - - Mock -CommandName Get-WebConfiguration -ParameterFilter { - $Filter -eq "$($MockWebsite.ItemXPath)/bindings/binding[last()]" - } -MockWith { - New-Module -AsCustomObject -ScriptBlock { - function AddSslCertificate {throw} - } - } - - It 'should throw the correct error' { - $ErrorId = 'WebBindingCertificate' - $ErrorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation - $ErrorMessage = $LocalizedData.ErrorWebBindingCertificate -f $MockBindingInfo.CertificateThumbprint, 'Exception calling "AddSslCertificate" with "2" argument(s): "ScriptHalted"' - $Exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $ErrorMessage - $ErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $Exception, $ErrorId, $ErrorCategory, $null - - { Update-WebsiteBinding ` - -Name $MockWebsite.Name ` - -BindingInfo $MockBindingInfo } | Should Throw $ErrorRecord - } - } - } - - Describe "$script:DSCResourceName\ConvertTo-CimLogCustomFields"{ - $mockLogCustomFields = @( - @{ - LogFieldName = 'LogField1' - SourceName = 'Accept-Encoding' - SourceType = 'RequestHeader' - } - @{ - LogFieldName = 'LogField2' - SourceName = 'Warning' - SourceType = 'ResponseHeader' - } - ) - - Context 'Expected behavior'{ - $Result = ConvertTo-CimLogCustomFields -InputObject $mockLogCustomFields - - It 'should return the LogFieldName' { - $Result[0].LogFieldName | Should Be $mockLogCustomFields[0].LogFieldName - $Result[0].LogFieldName | Should Be $mockLogCustomFields[0].LogFieldName - } - - It 'should return the SourceName' { - $Result[0].SourceName | Should Be $mockLogCustomFields[0].SourceName - $Result[0].SourceName | Should Be $mockLogCustomFields[0].SourceName - } - - It 'should return the SourceType' { - $Result[0].SourceType | Should Be $mockLogCustomFields[0].SourceType - $Result[0].SourceType | Should Be $mockLogCustomFields[0].SourceType - } - } - } - - Describe "$script:DSCResourceName\Test-LogCustomField"{ - $MockWebsiteName = 'ContosoSite' - - $MockCimLogCustomFields = @( - New-CimInstance -ClassName MSFT_xLogCustomFieldInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - LogFieldName = 'ClientEncoding' - SourceName = 'Accept-Encoding' - SourceType = 'RequestHeader' - } ` - -ClientOnly - ) - - Context 'LogCustomField in desired state'{ - $MockDesiredLogCustomFields = @{ - LogFieldName = 'ClientEncoding' - SourceName = 'Accept-Encoding' - SourceType = 'RequestHeader' - } - - Mock -CommandName Get-WebConfigurationProperty -MockWith { return $MockDesiredLogCustomFields } - - It 'should return True' { - Test-LogCustomField -Site $MockWebsiteName -LogCustomField $MockCimLogCustomFields | Should Be $True - } - } - - Context 'LogCustomField not in desired state'{ - $MockWrongLogCustomFields = @{ - LogFieldName = 'ClientEncoding' - SourceName = 'WrongSourceName' - SourceType = 'WrongSourceType' - } - - Mock -CommandName Get-WebConfigurationProperty -MockWith { return $MockWrongLogCustomFields } - - It 'should return False' { - Test-LogCustomField -Site $MockWebsiteName -LogCustomField $MockCimLogCustomFields | Should Be $False - } - } - - Context 'LogCustomField not present'{ - Mock -CommandName Get-WebConfigurationProperty -MockWith { return $false } - - It 'should return False' { - Test-LogCustomField -Site $MockWebsiteName -LogCustomField $MockCimLogCustomFields | Should Be $False - } - } - - } - - Describe "$script:DSCResourceName\Set-LogCustomField"{ - $MockWebsiteName = 'ContosoSite' - - $MockCimLogCustomFields = @( - New-CimInstance -ClassName MSFT_xLogCustomFieldInformation ` - -Namespace root/microsoft/Windows/DesiredStateConfiguration ` - -Property @{ - LogFieldName = 'ClientEncoding' - SourceName = 'Accept-Encoding' - SourceType = 'RequestHeader' - } ` - -ClientOnly - ) - - Context 'Create new LogCustomField'{ - Mock -CommandName Set-WebConfigurationProperty - - It 'should not throw an error' { - { Set-LogCustomField -Site $MockWebsiteName -LogCustomField $MockCimLogCustomFields } | Should Not Throw - } - - It 'should call should call expected mocks' { - Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 2 - } - } - - Context 'Modify existing LogCustomField'{ Mock -CommandName Set-WebConfigurationProperty - It 'should not throw an error' { + It 'Should not throw an error' { { Set-LogCustomField -Site $MockWebsiteName -LogCustomField $MockCimLogCustomFields } | Should Not Throw } - It 'should call should call expected mocks' { + It 'Should call should call expected mocks' { Assert-MockCalled -CommandName Set-WebConfigurationProperty -Exactly 2 } }