DSCResources/DSC_WebAppPool/DSC_WebAppPool.psm1

$modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules'

# Import the WebAdministrationDsc Common Modules
Import-Module -Name (Join-Path -Path $modulePath `
        -ChildPath (Join-Path -Path 'WebAdministrationDsc.Common' `
            -ChildPath 'WebAdministrationDsc.Common.psm1'))

Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common')

# Import Localization Strings
$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US'

# Writable properties except Ensure and Credential.
data PropertyData
{
    @(
        # General
        @{
            Name = 'State'
            Path = 'state'
        }
        @{
            Name = 'autoStart'
            Path = 'autoStart'
        }
        @{
            Name = 'CLRConfigFile'
            Path = 'CLRConfigFile'
        }
        @{
            Name = 'enable32BitAppOnWin64'
            Path = 'enable32BitAppOnWin64'
        }
        @{
            Name = 'enableConfigurationOverride'
            Path = 'enableConfigurationOverride'
        }
        @{
            Name = 'managedPipelineMode'
            Path = 'managedPipelineMode'
        }
        @{
            Name = 'managedRuntimeLoader'
            Path = 'managedRuntimeLoader'
        }
        @{
            Name = 'managedRuntimeVersion'
            Path = 'managedRuntimeVersion'
        }
        @{
            Name = 'passAnonymousToken'
            Path = 'passAnonymousToken'
        }
        @{
            Name = 'startMode'
            Path = 'startMode'
        }
        @{
            Name = 'queueLength'
            Path = 'queueLength'
        }

        # CPU
        @{
            Name = 'cpuAction'
            Path = 'cpu.action'
        }
        @{
            Name = 'cpuLimit'
            Path = 'cpu.limit'
        }
        @{
            Name = 'cpuResetInterval'
            Path = 'cpu.resetInterval'
        }
        @{
            Name = 'cpuSmpAffinitized'
            Path = 'cpu.smpAffinitized'
        }
        @{
            Name = 'cpuSmpProcessorAffinityMask'
            Path = 'cpu.smpProcessorAffinityMask'
        }
        @{
            Name = 'cpuSmpProcessorAffinityMask2'
            Path = 'cpu.smpProcessorAffinityMask2'
        }

        # Process Model
        @{
            Name = 'identityType'
            Path = 'processModel.identityType'
        }
        @{
            Name = 'idleTimeout'
            Path = 'processModel.idleTimeout'
        }
        @{
            Name = 'idleTimeoutAction'
            Path = 'processModel.idleTimeoutAction'
        }
        @{
            Name = 'loadUserProfile'
            Path = 'processModel.loadUserProfile'
        }
        @{
            Name = 'logEventOnProcessModel'
            Path = 'processModel.logEventOnProcessModel'
        }
        @{
            Name = 'logonType'
            Path = 'processModel.logonType'
        }
        @{
            Name = 'manualGroupMembership'
            Path = 'processModel.manualGroupMembership'
        }
        @{
            Name = 'maxProcesses'
            Path = 'processModel.maxProcesses'
        }
        @{
            Name = 'pingingEnabled'
            Path = 'processModel.pingingEnabled'
        }
        @{
            Name = 'pingInterval'
            Path = 'processModel.pingInterval'
        }
        @{
            Name = 'pingResponseTime'
            Path = 'processModel.pingResponseTime'
        }
        @{
            Name = 'setProfileEnvironment'
            Path = 'processModel.setProfileEnvironment'
        }
        @{
            Name = 'shutdownTimeLimit'
            Path = 'processModel.shutdownTimeLimit'
        }
        @{
            Name = 'startupTimeLimit'
            Path = 'processModel.startupTimeLimit'
        }

        # Process Orphaning
        @{
            Name = 'orphanActionExe'
            Path = 'failure.orphanActionExe'
        }
        @{
            Name = 'orphanActionParams'
            Path = 'failure.orphanActionParams'
        }
        @{
            Name = 'orphanWorkerProcess'
            Path = 'failure.orphanWorkerProcess'
        }

        # Rapid-Fail Protection
        @{
            Name = 'loadBalancerCapabilities'
            Path = 'failure.loadBalancerCapabilities'
        }
        @{
            Name = 'rapidFailProtection'
            Path = 'failure.rapidFailProtection'
        }
        @{
            Name = 'rapidFailProtectionInterval'
            Path = 'failure.rapidFailProtectionInterval'
        }
        @{
            Name = 'rapidFailProtectionMaxCrashes'
            Path = 'failure.rapidFailProtectionMaxCrashes'
        }
        @{
            Name = 'autoShutdownExe'
            Path = 'failure.autoShutdownExe'
        }
        @{
            Name = 'autoShutdownParams'
            Path = 'failure.autoShutdownParams'
        }

        # Recycling
        @{
            Name = 'disallowOverlappingRotation'
            Path = 'recycling.disallowOverlappingRotation'
        }
        @{
            Name = 'disallowRotationOnConfigChange'
            Path = 'recycling.disallowRotationOnConfigChange'
        }
        @{
            Name = 'logEventOnRecycle'
            Path = 'recycling.logEventOnRecycle'
        }
        @{
            Name = 'restartMemoryLimit'
            Path = 'recycling.periodicRestart.memory'
        }
        @{
            Name = 'restartPrivateMemoryLimit'
            Path = 'recycling.periodicRestart.privateMemory'
        }
        @{
            Name = 'restartRequestsLimit'
            Path = 'recycling.periodicRestart.requests'
        }
        @{
            Name = 'restartTimeLimit'
            Path = 'recycling.periodicRestart.time'
        }
        @{
            Name = 'restartSchedule'
            Path = 'recycling.periodicRestart.schedule'
        }
    )
}

# Properties that are specified as a single comma-separated string containing multiple flags
$script:commaSeparatedStringProperties = @(
    'logEventOnRecycle'
)

function Get-TargetResource
{
    <#
    .SYNOPSIS
        This will return a hashtable of results
    #>


    [CmdletBinding()]
    [OutputType([Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateLength(1, 64)]
        [String] $Name
    )

    Assert-Module -ModuleName WebAdministration

    # XPath -Filter is case-sensitive. Use Where-Object to get the target application pool by name.
    $appPool = Get-WebConfiguration -Filter '/system.applicationHost/applicationPools/add' |
        Where-Object -FilterScript {$_.name -eq $Name}

    $cimCredential = $null

    if ($null -eq $appPool)
    {
        Write-Verbose -Message ($script:localizedData.VerboseAppPoolNotFound -f $Name)

        $ensureResult = 'Absent'
    }
    else
    {
        Write-Verbose -Message ($script:localizedData.VerboseAppPoolFound -f $Name)

        $ensureResult = 'Present'

        if ($appPool.processModel.identityType -eq 'SpecificUser')
        {
            $cimCredential = New-CimInstance -ClientOnly `
                -ClassName MSFT_Credential `
                -Namespace root/microsoft/windows/DesiredStateConfiguration `
                -Property @{
                    UserName = [String]$appPool.processModel.userName
                    Password = [String]$appPool.processModel.password
                }
        }
    }

    $returnValue = @{
        Name = $Name
        Ensure = $ensureResult
        Credential = $cimCredential
    }

    $PropertyData.Where(
        {
            $_.Name -ne 'restartSchedule'
        }
    ).ForEach(
        {
            $property = Get-Property -Object $appPool -PropertyName $_.Path
            $returnValue.Add($_.Name, $property)
        }
    )

    $restartScheduleCurrent = [String[]]@(
        @($appPool.recycling.periodicRestart.schedule.Collection).ForEach('value')
    )

    $returnValue.Add('restartSchedule', $restartScheduleCurrent)

    return $returnValue
}

function Set-TargetResource
{
    <#
    .SYNOPSIS
        This will set the desired state
    #>


    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateLength(1, 64)]
        [String] $Name,

        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [String] $Ensure = 'Present',

        [Parameter()]
        [ValidateSet('Started', 'Stopped')]
        [String] $State,

        [Parameter()]
        [Boolean] $autoStart,

        [Parameter()]
        [String] $CLRConfigFile,

        [Parameter()]
        [Boolean] $enable32BitAppOnWin64,

        [Parameter()]
        [Boolean] $enableConfigurationOverride,

        [Parameter()]
        [ValidateSet('Integrated', 'Classic')]
        [String] $managedPipelineMode,

        [Parameter()]
        [String] $managedRuntimeLoader,

        [Parameter()]
        [ValidateSet('v4.0', 'v2.0', '')]
        [String] $managedRuntimeVersion,

        [Parameter()]
        [Boolean] $passAnonymousToken,

        [Parameter()]
        [ValidateSet('OnDemand', 'AlwaysRunning')]
        [String] $startMode,

        [Parameter()]
        [ValidateRange(10, 65535)]
        [UInt32] $queueLength,

        [Parameter()]
        [ValidateSet('NoAction', 'KillW3wp', 'Throttle', 'ThrottleUnderLoad')]
        [String] $cpuAction,

        [Parameter()]
        [ValidateRange(0, 100000)]
        [UInt32] $cpuLimit,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(0, 1440)]$valueInMinutes = [TimeSpan]::Parse($_).TotalMinutes); $?
        })]
        [String] $cpuResetInterval,

        [Parameter()]
        [Boolean] $cpuSmpAffinitized,

        [Parameter()]
        [UInt32] $cpuSmpProcessorAffinityMask,

        [Parameter()]
        [UInt32] $cpuSmpProcessorAffinityMask2,

        [Parameter()]
        [ValidateSet(
                'ApplicationPoolIdentity', 'LocalService', 'LocalSystem',
                'NetworkService', 'SpecificUser'
        )]
        [String] $identityType,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(0, 43200)]$valueInMinutes = [TimeSpan]::Parse($_).TotalMinutes); $?
        })]
        [String] $idleTimeout,

        [Parameter()]
        [ValidateSet('Terminate', 'Suspend')]
        [String] $idleTimeoutAction,

        [Parameter()]
        [Boolean] $loadUserProfile,

        [Parameter()]
        [String] $logEventOnProcessModel,

        [Parameter()]
        [ValidateSet('LogonBatch', 'LogonService')]
        [String] $logonType,

        [Parameter()]
        [Boolean] $manualGroupMembership,

        [Parameter()]
        [ValidateRange(0, 2147483647)]
        [UInt32] $maxProcesses,

        [Parameter()]
        [Boolean] $pingingEnabled,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(1, 4294967)]$valueInSeconds = [TimeSpan]::Parse($_).TotalSeconds); $?
        })]
        [String] $pingInterval,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(1, 4294967)]$valueInSeconds = [TimeSpan]::Parse($_).TotalSeconds); $?
        })]
        [String] $pingResponseTime,

        [Parameter()]
        [Boolean] $setProfileEnvironment,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(1, 4294967)]$valueInSeconds = [TimeSpan]::Parse($_).TotalSeconds); $?
        })]
        [String] $shutdownTimeLimit,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(1, 4294967)]$valueInSeconds = [TimeSpan]::Parse($_).TotalSeconds); $?
        })]
        [String] $startupTimeLimit,

        [Parameter()]
        [String] $orphanActionExe,

        [Parameter()]
        [String] $orphanActionParams,

        [Parameter()]
        [Boolean] $orphanWorkerProcess,

        [Parameter()]
        [ValidateSet('HttpLevel', 'TcpLevel')]
        [String] $loadBalancerCapabilities,

        [Parameter()]
        [Boolean] $rapidFailProtection,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(1, 144000)]$valueInMinutes = [TimeSpan]::Parse($_).TotalMinutes); $?
        })]
        [String] $rapidFailProtectionInterval,

        [Parameter()]
        [ValidateRange(0, 2147483647)]
        [UInt32] $rapidFailProtectionMaxCrashes,

        [Parameter()]
        [String] $autoShutdownExe,

        [Parameter()]
        [String] $autoShutdownParams,

        [Parameter()]
        [Boolean] $disallowOverlappingRotation,

        [Parameter()]
        [Boolean] $disallowRotationOnConfigChange,

        [Parameter()]
        [String] $logEventOnRecycle,

        [Parameter()]
        [UInt32] $restartMemoryLimit,

        [Parameter()]
        [UInt32] $restartPrivateMemoryLimit,

        [Parameter()]
        [UInt32] $restartRequestsLimit,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(0, 432000)]$valueInMinutes = [TimeSpan]::Parse($_).TotalMinutes); $?
        })]
        [String] $restartTimeLimit,

        [Parameter()]
        [ValidateScript({
            ($_ -eq '') -or
            (& {
                ([ValidateRange(0, 86399)]$valueInSeconds = [TimeSpan]::Parse($_).TotalSeconds); $?
            })
        })]
        [String[]] $restartSchedule
    )

    if (-not $PSCmdlet.ShouldProcess($Name))
    {
        return
    }

    Assert-Module -ModuleName WebAdministration

    $appPool = Get-WebConfiguration -Filter '/system.applicationHost/applicationPools/add' |
        Where-Object -FilterScript {$_.name -eq $Name}

    if ($Ensure -eq 'Present')
    {
        # Create Application Pool
        if ($null -eq $appPool)
        {
            Write-Verbose -Message ($script:localizedData.VerboseAppPoolNotFound -f $Name)
            Write-Verbose -Message ($script:localizedData.VerboseNewAppPool -f $Name)

            $appPool = New-WebAppPool -Name $Name -ErrorAction Stop
        }

        # Set Application Pool Properties
        if ($null -ne $appPool)
        {
            Write-Verbose -Message ($script:localizedData.VerboseAppPoolFound -f $Name)

            $PropertyData.Where(
                {
                    ($_.Name -in $PSBoundParameters.Keys) -and
                    ($_.Name -notin @('State', 'restartSchedule'))
                }
            ).ForEach(
                {
                    $propertyName = $_.Name
                    $propertyPath = $_.Path
                    $property = Get-Property -Object $appPool -PropertyName $propertyPath

                    if (
                        $PSBoundParameters[$propertyName] -ne $property
                    )
                    {
                        Write-Verbose -Message (
                            $script:localizedData.VerboseSetProperty -f $propertyName, $Name
                        )

                        Invoke-AppCmd -ArgumentList 'set', 'apppool', $Name, (
                            '/{0}:{1}' -f $propertyPath, $PSBoundParameters[$propertyName]
                        )
                    }
                }
            )

            if ($PSBoundParameters.ContainsKey('Credential'))
            {
                if ($PSBoundParameters['identityType'] -eq 'SpecificUser')
                {
                    if ($appPool.processModel.userName -ne $Credential.UserName)
                    {
                        Write-Verbose -Message (
                            $script:localizedData.VerboseSetProperty -f 'Credential (userName)', $Name
                        )

                        Invoke-AppCmd -ArgumentList 'set', 'apppool', $Name, (
                            '/processModel.userName:{0}' -f $Credential.UserName
                        )
                    }

                    $clearTextPassword = $Credential.GetNetworkCredential().Password

                    if ($appPool.processModel.password -cne $clearTextPassword)
                    {
                        Write-Verbose -Message (
                            $script:localizedData.VerboseSetProperty -f 'Credential (password)', $Name
                        )

                        Invoke-AppCmd -ArgumentList 'set', 'apppool', $Name, (
                            '/processModel.password:{0}' -f $clearTextPassword
                        )
                    }
                }
                else
                {
                    Write-Verbose -Message ($script:localizedData.VerboseCredentialToBeIgnored)
                }
            }

            # Ensure userName and password are cleared if identityType isn't set to SpecificUser.
            if (
                (
                    (
                        ($PSBoundParameters.ContainsKey('identityType') -eq $true) -and
                        ($PSBoundParameters['identityType'] -ne 'SpecificUser')
                    ) -or
                    (
                        ($PSBoundParameters.ContainsKey('identityType') -eq $false) -and
                        ($appPool.processModel.identityType -ne 'SpecificUser')
                    )
                ) -and
                (
                    ([String]::IsNullOrEmpty($appPool.processModel.userName) -eq $false) -or
                    ([String]::IsNullOrEmpty($appPool.processModel.password) -eq $false)
                )
            )
            {
                Write-Verbose -Message ($script:localizedData.VerboseClearCredential -f $Name)

                Invoke-AppCmd -ArgumentList 'set', 'apppool', $Name, '/processModel.userName:'
                Invoke-AppCmd -ArgumentList 'set', 'apppool', $Name, '/processModel.password:'
            }

            if ($PSBoundParameters.ContainsKey('restartSchedule'))
            {
                # Normalize the restartSchedule array values.
                $restartScheduleDesired = [String[]]@(
                    $restartSchedule.Where(
                        {
                            $_ -ne ''
                        }
                    ).ForEach(
                        {
                            [TimeSpan]::Parse($_).ToString('hh\:mm\:ss')
                        }
                    ) |
                    Select-Object -Unique
                )

                $restartScheduleCurrent = [String[]]@(
                    @($appPool.recycling.periodicRestart.schedule.Collection).ForEach('value')
                )

                Compare-Object -ReferenceObject $restartScheduleDesired `
                    -DifferenceObject $restartScheduleCurrent |
                        ForEach-Object -Process {

                            # Add value
                            if ($_.SideIndicator -eq '<=')
                            {
                                Write-Verbose -Message (
                                    $script:localizedData.VerboseRestartScheduleValueAdd -f
                                        $_.InputObject, $Name
                                )

                                Invoke-AppCmd -ArgumentList 'set', 'apppool', $Name, (
                                    "/+recycling.periodicRestart.schedule.[value='{0}']" -f $_.InputObject
                                )
                            }
                            # Remove value
                            else
                            {
                                Write-Verbose -Message (
                                    $script:localizedData.VerboseRestartScheduleValueRemove -f
                                        $_.InputObject, $Name
                                )

                                Invoke-AppCmd -ArgumentList 'set', 'apppool', $Name, (
                                    "/-recycling.periodicRestart.schedule.[value='{0}']" -f $_.InputObject
                                )
                            }

                        }
            }

            if ($PSBoundParameters.ContainsKey('State') -and $appPool.state -ne $State)
            {
                if ($State -eq 'Started')
                {
                    Write-Verbose -Message ($script:localizedData.VerboseStartAppPool -f $Name)

                    Start-WebAppPool -Name $Name -ErrorAction Stop
                }
                else
                {
                    Write-Verbose -Message ($script:localizedData.VerboseStopAppPool -f $Name)

                    Stop-WebAppPool -Name $Name -ErrorAction Stop
                }
            }
        }
    }
    else
    {
        # Remove Application Pool
        if ($null -ne $appPool)
        {
            Write-Verbose -Message ($script:localizedData.VerboseAppPoolFound -f $Name)

            if ($appPool.state -eq 'Started')
            {
                Write-Verbose -Message ($script:localizedData.VerboseStopAppPool -f $Name)

                Stop-WebAppPool -Name $Name -ErrorAction Stop
            }

            Write-Verbose -Message ($script:localizedData.VerboseRemoveAppPool -f $Name)

            Remove-WebAppPool -Name $Name -ErrorAction Stop
        }
        else
        {
            Write-Verbose -Message ($script:localizedData.VerboseAppPoolNotFound -f $Name)
        }
    }
}

function Test-TargetResource
{
    <#
    .SYNOPSIS
        This tests the desired state. If the state is not correct it will return $false.
        If the state is correct it will return $true
    #>


    [OutputType([Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateLength(1, 64)]
        [String] $Name,

        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [String] $Ensure = 'Present',

        [Parameter()]
        [ValidateSet('Started', 'Stopped')]
        [String] $State,

        [Parameter()]
        [Boolean] $autoStart,

        [Parameter()]
        [String] $CLRConfigFile,

        [Parameter()]
        [Boolean] $enable32BitAppOnWin64,

        [Parameter()]
        [Boolean] $enableConfigurationOverride,

        [Parameter()]
        [ValidateSet('Integrated', 'Classic')]
        [String] $managedPipelineMode,

        [Parameter()]
        [String] $managedRuntimeLoader,

        [Parameter()]
        [ValidateSet('v4.0', 'v2.0', '')]
        [String] $managedRuntimeVersion,

        [Parameter()]
        [Boolean] $passAnonymousToken,

        [Parameter()]
        [ValidateSet('OnDemand', 'AlwaysRunning')]
        [String] $startMode,

        [Parameter()]
        [ValidateRange(10, 65535)]
        [UInt32] $queueLength,

        [Parameter()]
        [ValidateSet('NoAction', 'KillW3wp', 'Throttle', 'ThrottleUnderLoad')]
        [String] $cpuAction,

        [Parameter()]
        [ValidateRange(0, 100000)]
        [UInt32] $cpuLimit,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(0, 1440)]$valueInMinutes = [TimeSpan]::Parse($_).TotalMinutes); $?
        })]
        [String] $cpuResetInterval,

        [Parameter()]
        [Boolean] $cpuSmpAffinitized,

        [Parameter()]
        [UInt32] $cpuSmpProcessorAffinityMask,

        [Parameter()]
        [UInt32] $cpuSmpProcessorAffinityMask2,

        [Parameter()]
        [ValidateSet(
                'ApplicationPoolIdentity', 'LocalService', 'LocalSystem',
                'NetworkService', 'SpecificUser'
        )]
        [String] $identityType,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(0, 43200)]$valueInMinutes = [TimeSpan]::Parse($_).TotalMinutes); $?
        })]
        [String] $idleTimeout,

        [Parameter()]
        [ValidateSet('Terminate', 'Suspend')]
        [String] $idleTimeoutAction,

        [Parameter()]
        [Boolean] $loadUserProfile,

        [Parameter()]
        [String] $logEventOnProcessModel,

        [Parameter()]
        [ValidateSet('LogonBatch', 'LogonService')]
        [String] $logonType,

        [Parameter()]
        [Boolean] $manualGroupMembership,

        [Parameter()]
        [ValidateRange(0, 2147483647)]
        [UInt32] $maxProcesses,

        [Parameter()]
        [Boolean] $pingingEnabled,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(1, 4294967)]$valueInSeconds = [TimeSpan]::Parse($_).TotalSeconds); $?
        })]
        [String] $pingInterval,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(1, 4294967)]$valueInSeconds = [TimeSpan]::Parse($_).TotalSeconds); $?
        })]
        [String] $pingResponseTime,

        [Parameter()]
        [Boolean] $setProfileEnvironment,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(1, 4294967)]$valueInSeconds = [TimeSpan]::Parse($_).TotalSeconds); $?
        })]
        [String] $shutdownTimeLimit,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(1, 4294967)]$valueInSeconds = [TimeSpan]::Parse($_).TotalSeconds); $?
        })]
        [String] $startupTimeLimit,

        [Parameter()]
        [String] $orphanActionExe,

        [Parameter()]
        [String] $orphanActionParams,

        [Parameter()]
        [Boolean] $orphanWorkerProcess,

        [Parameter()]
        [ValidateSet('HttpLevel', 'TcpLevel')]
        [String] $loadBalancerCapabilities,

        [Parameter()]
        [Boolean] $rapidFailProtection,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(1, 144000)]$valueInMinutes = [TimeSpan]::Parse($_).TotalMinutes); $?
        })]
        [String] $rapidFailProtectionInterval,

        [Parameter()]
        [ValidateRange(0, 2147483647)]
        [UInt32] $rapidFailProtectionMaxCrashes,

        [Parameter()]
        [String] $autoShutdownExe,

        [Parameter()]
        [String] $autoShutdownParams,

        [Parameter()]
        [Boolean] $disallowOverlappingRotation,

        [Parameter()]
        [Boolean] $disallowRotationOnConfigChange,

        [Parameter()]
        [String] $logEventOnRecycle,

        [Parameter()]
        [UInt32] $restartMemoryLimit,

        [Parameter()]
        [UInt32] $restartPrivateMemoryLimit,

        [Parameter()]
        [UInt32] $restartRequestsLimit,

        [Parameter()]
        [ValidateScript({
            ([ValidateRange(0, 432000)]$valueInMinutes = [TimeSpan]::Parse($_).TotalMinutes); $?
        })]
        [String] $restartTimeLimit,

        [Parameter()]
        [ValidateScript({
            ($_ -eq '') -or
            (& {
                ([ValidateRange(0, 86399)]$valueInSeconds = [TimeSpan]::Parse($_).TotalSeconds); $?
            })
        })]
        [String[]] $restartSchedule
    )

    Assert-Module -ModuleName WebAdministration

    $inDesiredState = $true

    $appPool = Get-WebConfiguration -Filter '/system.applicationHost/applicationPools/add' |
        Where-Object -FilterScript {$_.name -eq $Name}

    if (
        ($Ensure -eq 'Absent' -and $null -ne $appPool) -or
        ($Ensure -eq 'Present' -and $null -eq $appPool)
    )
    {
        $inDesiredState = $false

        if ($null -ne $appPool)
        {
            Write-Verbose -Message ($script:localizedData.VerboseAppPoolFound -f $Name)
        }
        else
        {
            Write-Verbose -Message ($script:localizedData.VerboseAppPoolNotFound -f $Name)
        }

        Write-Verbose -Message ($script:localizedData.VerboseEnsureNotInDesiredState -f $Name)
    }

    if ($Ensure -eq 'Present' -and $null -ne $appPool)
    {
        Write-Verbose -Message ($script:localizedData.VerboseAppPoolFound -f $Name)

        $PropertyData.Where(
            {
                ($_.Name -in $PSBoundParameters.Keys) -and
                ($_.Name -ne 'restartSchedule')
            }
        ).ForEach(
            {
                $propertyName = $_.Name
                $propertyPath = $_.Path
                $property = Get-Property -Object $appPool -PropertyName $propertyPath

                # First check if the property is a single comma-separated string containing multiple flags, split and compare membership if so
                if ($propertyName -in $script:commaSeparatedStringProperties)
                {
                    $currentPropertyCollection = $property.Split(',')
                    $expectedPropertyCollection = $PSBoundParameters[$propertyName].Split(',')

                    $compareResult = @(Compare-Object -ReferenceObject $currentPropertyCollection -DifferenceObject $expectedPropertyCollection)
                    if ($compareResult.Length -ne 0)
                    {
                        Write-Verbose -Message (
                            $script:localizedData.VerbosePropertyNotInDesiredState -f $propertyName, $Name
                        )

                        $inDesiredState = $false
                    }
                }
                elseif (
                    $PSBoundParameters[$propertyName] -ne $property
                )
                {
                    Write-Verbose -Message (
                        $script:localizedData.VerbosePropertyNotInDesiredState -f $propertyName, $Name
                    )

                    $inDesiredState = $false
                }
            }
        )

        if ($PSBoundParameters.ContainsKey('Credential'))
        {
            if ($PSBoundParameters['identityType'] -eq 'SpecificUser')
            {
                if ($appPool.processModel.userName -ne $Credential.UserName)
                {
                    Write-Verbose -Message (
                        $script:localizedData.VerbosePropertyNotInDesiredState -f
                            'Credential (userName)', $Name
                    )

                    $inDesiredState = $false
                }

                $clearTextPassword = $Credential.GetNetworkCredential().Password

                if ($appPool.processModel.password -cne $clearTextPassword)
                {
                    Write-Verbose -Message (
                        $script:localizedData.VerbosePropertyNotInDesiredState -f
                            'Credential (password)', $Name
                    )

                    $inDesiredState = $false
                }
            }
            else
            {
                Write-Verbose -Message ($script:localizedData.VerboseCredentialToBeIgnored)
            }
        }

        # Ensure userName and password are cleared if identityType isn't set to SpecificUser.
        if (
            (
                (
                    ($PSBoundParameters.ContainsKey('identityType') -eq $true) -and
                    ($PSBoundParameters['identityType'] -ne 'SpecificUser')
                ) -or
                (
                    ($PSBoundParameters.ContainsKey('identityType') -eq $false) -and
                    ($appPool.processModel.identityType -ne 'SpecificUser')
                )
            ) -and
            (
                ([String]::IsNullOrEmpty($appPool.processModel.userName) -eq $false) -or
                ([String]::IsNullOrEmpty($appPool.processModel.password) -eq $false)
            )
        )
        {
            Write-Verbose -Message ($script:localizedData.VerboseCredentialToBeCleared -f $Name)

            $inDesiredState = $false
        }

        if ($PSBoundParameters.ContainsKey('restartSchedule'))
        {
            # Normalize the restartSchedule array values.
            $restartScheduleDesired = [String[]]@(
                $restartSchedule.Where(
                    {
                        $_ -ne ''
                    }
                ).ForEach(
                    {
                        [TimeSpan]::Parse($_).ToString('hh\:mm\:ss')
                    }
                ) |
                Select-Object -Unique
            )

            $restartScheduleCurrent = [String[]]@(
                @($appPool.recycling.periodicRestart.schedule.Collection).ForEach('value')
            )

            if (
                Compare-Object -ReferenceObject $restartScheduleDesired `
                    -DifferenceObject $restartScheduleCurrent
            )
            {
                Write-Verbose -Message (
                    $script:localizedData.VerbosePropertyNotInDesiredState -f 'restartSchedule', $Name
                )

                $inDesiredState = $false
            }
        }
    }

    if ($inDesiredState -eq $true)
    {
        Write-Verbose -Message ($script:localizedData.VerboseResourceInDesiredState)
    }
    else
    {
        Write-Verbose -Message ($script:localizedData.VerboseResourceNotInDesiredState)
    }

    return $inDesiredState
}

#region Helper Functions

function Get-Property
{
    param
    (
        [Parameter()]
        [object] $Object,

        [Parameter()]
        [string] $PropertyName)

    $parts = $PropertyName.Split('.')
    $firstPart = $parts[0]

    $value = $Object.$firstPart
    if ($parts.Count -gt 1)
    {
        $newParts = @()
        1..($parts.Count -1) | ForEach-Object {
            $newParts += $parts[$_]
        }

        $newName = ($newParts -join '.')
        return Get-Property -Object $value -PropertyName $newName
    }
    else
    {
        return $value
    }
}

<#
    .SYNOPSIS
        Runs appcmd.exe - if there's an error then the application will terminate
 
    .PARAMETER ArgumentList
        Optional list of string arguments to be passed into appcmd.exe
 
#>

function Invoke-AppCmd
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [String[]] $ArgumentList
    )

    <#
            This is a local preference for the function which will terminate
            the program if there's an error invoking appcmd.exe
    #>

    $ErrorActionPreference = 'Stop'

    $appcmdFilePath = "$env:SystemRoot\System32\inetsrv\appcmd.exe"

    $appcmdResult = $(& $appcmdFilePath $ArgumentList)
    Write-Verbose -Message $($appcmdResult).ToString()

    if ($LASTEXITCODE -ne 0)
    {
        $errorMessage = $script:localizedData.ErrorAppCmdNonZeroExitCode -f $LASTEXITCODE

        New-TerminatingError -ErrorId 'ErrorAppCmdNonZeroExitCode' `
            -ErrorMessage $errorMessage `
            -ErrorCategory 'InvalidResult'
    }
}

#endregion Helper Functions

Export-ModuleMember -Function *-TargetResource