DSCResources/DSC_CMMaintenanceWindows/DSC_CMMaintenanceWindows.psm1

$script:dscResourceCommonPath = Join-Path (Join-Path -Path (Split-Path -Parent -Path (Split-Path -Parent -Path $PsScriptRoot)) -ChildPath Modules) -ChildPath DscResource.Common
$script:configMgrResourcehelper = Join-Path (Join-Path -Path (Split-Path -Parent -Path (Split-Path -Parent -Path $PsScriptRoot)) -ChildPath Modules) -ChildPath ConfigMgrCBDsc.ResourceHelper

Import-Module -Name $script:dscResourceCommonPath
Import-Module -Name $script:configMgrResourcehelper

$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US'

<#
    .SYNOPSIS
        This will return a hashtable of results.
 
    .PARAMETER SiteCode
        Specifies the site code for Configuration Manager site.
 
    .PARAMETER CollectionName
        Specifies the collection name for the maintenance window.
 
    .PARAMETER Name
        Specifies the name for the maintenance window.
#>

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $SiteCode,

        [Parameter(Mandatory = $true)]
        [String]
        $CollectionName,

        [Parameter(Mandatory = $true)]
        [String]
        $Name
    )

    Write-Verbose -Message $script:localizedData.RetrieveSettingValue
    Import-ConfigMgrPowerShellModule -SiteCode $SiteCode
    Set-Location -Path "$($SiteCode):\"

    if (Get-CMCollection -Name $CollectionName)
    {
        $collect = 'Present'
    }
    else
    {
        $collect = 'Absent'
    }

    $exWindows = Get-CMMaintenanceWindow -CollectionName $CollectionName -MaintenanceWindowName $Name
    $windows = $exWindows | where-Object -FilterScript {$_.Name -eq $Name}

    if ($windows)
    {
        $type = switch ($windows.ServiceWindowType)
        {
            1 { 'Any' }
            4 { 'SoftwareUpdatesOnly' }
            5 { 'TaskSequencesOnly' }
        }

        if ($windows.ServiceWindowSchedules)
        {
            $schedule = Get-CMSchedule -ScheduleString $windows.ServiceWindowSchedules
        }

        $sure = 'Present'
    }
    else
    {
        $sure = 'Absent'
    }

    return @{
        SiteCode           = $SiteCode
        CollectionName     = $CollectionName
        Name               = $Name
        ServiceWindowsType = $type
        IsEnabled          = $windows.IsEnabled
        Ensure             = $sure
        HourDuration       = $schedule.HourDuration
        MinuteDuration     = $schedule.MinuteDuration
        Start              = $schedule.Start
        ScheduleType       = $schedule.ScheduleType
        DayOfWeek          = $schedule.DayOfWeek
        MonthlyWeekOrder   = $schedule.WeekOrder
        DayOfMonth         = $schedule.MonthDay
        RecurInterval      = $schedule.RecurInterval
        Description        = $windows.Description
        CollectionStatus   = $collect
    }
}

<#
    .SYNOPSIS
        This will set the desired state.
 
    .PARAMETER SiteCode
        Specifies the site code for Configuration Manager site.
 
    .PARAMETER CollectionName
        Specifies the collection name for the maintenance window.
 
    .PARAMETER Name
        Specifies the name for the maintenance window.
 
    .PARAMETER ServiceWindowsType
        Specifies what the maintenance window will apply to.
 
    .PARAMETER Start
        Specifies the start date and start time for the maintenance window Month/Day/Year, example 1/1/2020 02:00.
 
    .PARAMETER ScheduleType
        Specifies the schedule type for the maintenance window.
 
    .PARAMETER RecurInterval
        Specifies how often the ScheduleType is run.
 
    .PARAMETER MonthlyByWeek
        Specifies week order for MonthlyByWeek schedule type.
 
    .PARAMETER DayOfWeek
        Specifies the day of week name for MonthlyByWeek and Weekly schedules.
 
    .PARAMETER DayOfMonth
        Specifies the day number for MonthlyByDay schedules.
        Note specifying 0 sets the schedule to run the last day of the month.
 
    .PARAMETER HourDuration
        Specifies the duration for the maintenance window in hours, max value 23.
 
    .PARAMETER MinuteDuration
        Specifies the duration for the maintenance window in minutes, max value 59.
 
    .PARAMETER IsEnabled
        Specifies if the maintenance window is enabled, default value is enabled.
 
    .PARAMETER Ensure
        Specifies whether the maintenance window is present or absent.
#>

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $SiteCode,

        [Parameter(Mandatory = $true)]
        [String]
        $CollectionName,

        [Parameter(Mandatory = $true)]
        [String]
        $Name,

        [Parameter()]
        [ValidateSet('Any','SoftwareUpdatesOnly','TaskSequencesOnly')]
        [String]
        $ServiceWindowsType,

        [Parameter()]
        [String]
        $Start,

        [Parameter()]
        [ValidateSet('MonthlyByDay','MonthlyByWeek','Weekly','Days','None')]
        [String]
        $ScheduleType,

        [Parameter()]
        [UInt32]
        $RecurInterval,

        [Parameter()]
        [ValidateSet('First','Second','Third','Fourth','Last')]
        [String]
        $MonthlyWeekOrder,

        [Parameter()]
        [ValidateSet('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday')]
        [String]
        $DayOfWeek,

        [Parameter()]
        [ValidateRange(0,31)]
        [UInt32]
        $DayOfMonth,

        [Parameter()]
        [ValidateRange(1,23)]
        [UInt32]
        $HourDuration,

        [Parameter()]
        [ValidateRange(5,59)]
        [UInt32]
        $MinuteDuration,

        [Parameter()]
        [Boolean]
        $IsEnabled = $true,

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

    Import-ConfigMgrPowerShellModule -SiteCode $SiteCode
    Set-Location -Path "$($SiteCode):\"

    try
    {
        $state = Get-TargetResource -CollectionName $CollectionName -Name $Name -SiteCode $SiteCode

        if ($Ensure -eq 'Present')
        {
            if ($state.CollectionStatus -eq 'Absent')
            {
                throw ($script:localizedData.MissingCollection -f $CollectionName)
            }

            if ((-not $PSBoundParameters.ContainsKey('ScheduleType')) -or
                (($PSBoundParameters.ContainsKey('ScheduleType')) -and
                (-not $PSBoundParameters.ContainsKey('MinuteDuration') -and
                -not $PSBoundParameters.ContainsKey('HourDuration'))))
            {
                throw ($script:localizedData.MissingWindowParam -f $Name)
            }

            if ($PSBoundParameters.ContainsKey('HourDuration') -and $PSBoundParameters.ContainsKey('MinuteDuration'))
            {
                throw $script:localizedData.MixedDuration
            }

            if ($PSBoundParameters.ContainsKey('ServiceWindowsType') -and $state.ServiceWindowsType -ne $ServiceWindowsType)
            {
                Write-Verbose -Message ($script:localizedData.ChangingApplyTo -f $state.ServiceWindowsType, $ServiceWindowsType)
                $buildingParams += @{
                    ApplyTo = $ServiceWindowsType
                }
            }

            if ($state.IsEnabled -ne $IsEnabled)
            {
                Write-Verbose -Message ($script:localizedData.ChangingIsEnabled -f $state.IsEnabled, $IsEnabled)
                $buildingParams += @{
                    IsEnabled = $IsEnabled
                }
            }

            if ($ScheduleType)
            {
                $valuesToValidate = @('ScheduleType','RecurInterval','MonthlyWeekOrder','DayOfWeek','DayOfMonth','Start',
                                      'HourDuration','MinuteDuration')
                foreach ($item in $valuesToValidate)
                {
                    if ($PSBoundParameters.ContainsKey($item))
                    {
                        $scheduleCheck += @{
                            $item = $PSBoundParameters[$item]
                        }
                    }
                }

                $schedResult = Test-CMSchedule @scheduleCheck -State $state
            }

            if ($schedResult -eq $false)
            {
                $sched = Set-CMSchedule @scheduleCheck
                $newSchedule = New-CMSchedule @sched

                Write-Verbose -Message $script:localizedData.NewSchedule
                $buildingParams += @{
                    Schedule = $newSchedule
                }
            }

            if ($state.Ensure -eq 'Absent')
            {
                $buildingParams += @{
                    CollectionName = $CollectionName
                    Name           = $Name
                }

                Write-Verbose -Message $script:localizedData.NewWindow
                New-CMMaintenanceWindow @buildingParams
            }
            else
            {
                $buildingParams += @{
                    CollectionName        = $CollectionName
                    MaintenanceWindowName = $Name
                }

                Write-Verbose -Message $script:localizedData.ModifyWindow
                Set-CMMaintenanceWindow @buildingParams
            }
        }
        elseif ($Ensure -eq 'Absent' -and $state.Ensure -eq 'Present')
        {
            Write-Verbose -Message ($script:localizedData.RemoveMW -f $Name)
            Remove-CMMaintenanceWindow -CollectionName $CollectionName -MaintenanceWindowName $Name
        }
    }
    catch
    {
        throw $_
    }
    finally
    {
        Set-Location -Path "$env:temp"
    }
}

<#
    .SYNOPSIS
        This will test the desired state.
 
    .PARAMETER SiteCode
        Specifies the site code for Configuration Manager site.
 
    .PARAMETER CollectionName
        Specifies the collection name for the maintenance window.
 
    .PARAMETER Name
        Specifies the name for the maintenance window.
 
    .PARAMETER ServiceWindowsType
        Specifies what the maintenance window will apply to.
 
    .PARAMETER Start
        Specifies the start date and start time for the maintenance window Month/Day/Year, example 1/1/2020 02:00.
 
    .PARAMETER ScheduleType
        Specifies the schedule type for the maintenance window.
 
    .PARAMETER RecurInterval
        Specifies how often the ScheduleType is run.
 
    .PARAMETER MonthlyByWeek
        Specifies week order for MonthlyByWeek schedule type.
 
    .PARAMETER DayOfWeek
        Specifies the day of week name for MonthlyByWeek and Weekly schedules.
 
    .PARAMETER DayOfMonth
        Specifies the day number for MonthlyByDay schedules.
        Note specifying 0 sets the schedule to run the last day of the month.
 
    .PARAMETER HourDuration
        Specifies the duration for the maintenance window in hours, max value 23.
 
    .PARAMETER MinuteDuration
        Specifies the duration for the maintenance window in minutes, max value 59.
 
    .PARAMETER IsEnabled
        Specifies if the maintenance window is enabled, default value is enabled.
 
    .PARAMETER Ensure
        Specifies whether the maintenance window is present or absent.
#>

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $SiteCode,

        [Parameter(Mandatory = $true)]
        [String]
        $CollectionName,

        [Parameter(Mandatory = $true)]
        [String]
        $Name,

        [Parameter()]
        [ValidateSet('Any','SoftwareUpdatesOnly','TaskSequencesOnly')]
        [String]
        $ServiceWindowsType,

        [Parameter()]
        [String]
        $Start,

        [Parameter()]
        [ValidateSet('MonthlyByDay','MonthlyByWeek','Weekly','Days','None')]
        [String]
        $ScheduleType,

        [Parameter()]
        [UInt32]
        $RecurInterval,

        [Parameter()]
        [ValidateSet('First','Second','Third','Fourth','Last')]
        [String]
        $MonthlyWeekOrder,

        [Parameter()]
        [ValidateSet('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday')]
        [String]
        $DayOfWeek,

        [Parameter()]
        [ValidateRange(0,31)]
        [UInt32]
        $DayOfMonth,

        [Parameter()]
        [ValidateRange(1,23)]
        [UInt32]
        $HourDuration,

        [Parameter()]
        [ValidateRange(5,59)]
        [UInt32]
        $MinuteDuration,

        [Parameter()]
        [Boolean]
        $IsEnabled = $true,

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

    Import-ConfigMgrPowerShellModule -SiteCode $SiteCode
    Set-Location -Path "$($SiteCode):\"
    $state = Get-TargetResource -SiteCode $SiteCode -CollectionName $CollectionName -Name $Name
    $result = $true

    if ($Ensure -eq 'Present')
    {
        if ($PSBoundParameters.ContainsKey('HourDuration') -and $PSBoundParameters.ContainsKey('MinuteDuration'))
        {
            Write-Warning -Message $script:localizedData.MixedDuration
        }

        if ($state.CollectionStatus -eq 'Absent')
        {
            Write-Warning -Message ($script:localizedData.MissingCollection -f $CollectionName)
            $result = $false
        }
        elseif ($state.Ensure -eq 'Absent')
        {
            if ((-not $PSBoundParameters.ContainsKey('ScheduleType')) -or
                (($PSBoundParameters.ContainsKey('ScheduleType')) -and
                (-not $PSBoundParameters.ContainsKey('MinuteDuration') -and
                -not $PSBoundParameters.ContainsKey('HourDuration'))))
            {
                Write-Warning -Message ($script:localizedData.MissingWindowParam -f $Name)
            }

            Write-Verbose -Message ($script:localizedData.MissingWindow -f $Name)
            $result = $false
        }
        else
        {
            $valuesToCheck = @('CollectionName','Name','IsEnabled','Ensure','ServiceWindowsType')

            if (-not $PSBoundParameters.ContainsKey('IsEnabled'))
            {
                $PSBoundParameters.Add('IsEnabled',$true)
            }

            $testParams = @{
                CurrentValues = $state
                DesiredValues = $PSBoundParameters
                ValuesToCheck = $valuesToCheck
            }

            $mainState = Test-DscParameterState @testParams -Verbose

            if ($ScheduleType)
            {
                $valuesToValidate = @('ScheduleType','RecurInterval','MonthlyWeekOrder','DayOfWeek','DayOfMonth','Start',
                                      'HourDuration','MinuteDuration')
                foreach ($item in $valuesToValidate)
                {
                    if ($PSBoundParameters.ContainsKey($item))
                    {
                        $scheduleCheck += @{
                            $item = $PSBoundParameters[$item]
                        }
                    }
                }

                $schedResult = Test-CMSchedule @scheduleCheck -State $state
            }

            if ($mainState -ne $true -or $schedResult -ne $true -or $result -ne $true)
            {
                $result = $false
            }
            else
            {
                $result = $true
            }
        }
    }
    elseif ($state.Ensure -eq 'Present')
    {
        Write-Verbose -Message $script:localizedData.Absent
        $result = $false
    }

    Write-Verbose -Message ($script:localizedData.TestState -f $result)
    Set-Location -Path "$env:temp"
    return $result
}

Export-ModuleMember -Function *-TargetResource