Modules/ConfigMgrCBDsc.ResourceHelper/ConfigMgrCBDsc.ResourceHelper.psm1

# Localized messages
data LocalizedData
{
    # Culture="en-US"
    ConvertFrom-StringData -StringData @'
    ModuleNotFound = Please ensure that the PowerShell module for role {0} is installed.
'@

}

$script:dscResourceCommonPath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common'
Import-Module -Name $script:dscResourceCommonPath

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

<#
    .SYNOPSIS
        Import Configuration Manager module commands.
 
    .PARAMTER SiteCode
        Specifies the site code for configuration manager.
#>

function Import-ConfigMgrPowerShellModule
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $SiteCode
    )

    if ((Test-Path -Path "$($SiteCode):\") -eq $false)
    {
        $getCim = @{
            ClassName = 'SMS_Site'
            Namespace = "root\sms\site_$SiteCode"
        }

        $siteInfo = Get-CimInstance @getCim | Where-Object -FilterScript {$_.SiteCode -eq $SiteCode}
        $sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
        $baseRegKeyPath = "Registry::HKEY_Users\$sid\Software\Microsoft"
        $createKeys = @('ConfigMgr10','AdminUI','MRU','1')

        foreach ($key in $createKeys)
        {
            if (-not (Test-Path -Path "$baseRegKeyPath\$key"))
            {
                New-Item -Path $baseRegKeyPath -Name $key |Out-Null
            }

            $baseRegKeyPath += "\$key"
        }

        $regProperties = (Get-ItemProperty -Path $baseRegKeyPath -ErrorAction SilentlyContinue)

        $values = @{
            ServerName = $siteInfo.ServerName
            SiteName   = $siteInfo.SiteName
            SiteCode   = $siteInfo.SiteCode
            DomainName = ($siteinfo.ServerName.SubString($siteinfo.ServerName.Indexof('.') + 1))
        }

        foreach ($value in $values.GetEnumerator())
        {
            if ($($regProperties.$($value.Name)) -ne $value.Value)
            {
                Set-ItemProperty -Path $baseRegKeyPath -Name $value.Name -Value $value.Value | Out-Null
            }
        }

        Set-ConfigmgrCert

        try
        {
            Import-Module -Name (Join-Path $(Split-Path $ENV:SMS_ADMIN_UI_PATH) ConfigurationManager.psd1) -Global
        }
        catch
        {
            throw "Failure to import SCCM Cmdlets."
        }
    }

    if ((Get-Module -Name ConfigurationManager).Version -lt '5.1902')
    {
        throw "Incorrect version of Configuration Manager Powershell to use this module"
    }
}

<#
    .SYNOPSIS
        Imports the configuration manager powershell certificate to Trusted Publisher.
#>

function Set-ConfigMgrCert
{
    param ()

    $configCert = Get-AuthenticodeSignature -FilePath (Join-Path $(Split-Path $ENV:SMS_ADMIN_UI_PATH) ConfigurationManager.psd1)

    $store = Get-Item -Path Cert:\LocalMachine\TrustedPublisher
    $store.Open('ReadWrite')

    if ($store.Certificates -notcontains $configCert.SignerCertificate)
    {
        $store.Add($configCert.SignerCertificate)
    }

    $store.Close()
}

<#
    .SYNOPSIS
        Converts the CIDR and IPAddress.
 
    .PARAMETER IPAddress
        Specifies the network address.
 
    .PARAMETER Cidr
        Specifies the network mask value.
#>

function Convert-CidrToIP
{
    [CmdLetBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [IPAddress]
        $IPAddress,

        [Parameter(Mandatory = $true)]
        [ValidateRange(0,32)]
        [Int16]
        $Cidr
    )

    $CidrBits = ('1' * $Cidr).PadRight(32, '0')
    $octets = $CidrBits -Split '(.{8})' -ne ''
    $mask = ($octets | ForEach-Object -Process {[Convert]::ToInt32($_, 2) }) -Join '.'

    $ip = [IPAddress](($IPAddress).Address -Band ([IPAddress]$mask).Address)

    return  @{
        NetworkAddress = $ip.IPAddressToString
        Subnetmask     = $mask
        Cidr           = $Cidr
    }
}

<#
    .SYNOPSIS
        Converts CMSchedule objects to a readable and workable format.
 
    .PARAMETER ScheduleString
        Specifies the schedule string to convert.
 
    .PARAMETER CimClassName
        Specifies the name of the EmbeddedInstance for the schedule object.
#>

function ConvertTo-CimCMScheduleString
{
    [CmdletBinding()]
    [OutputType([Microsoft.Management.Infrastructure.CimInstance])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $ScheduleString,

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

    $schedule = Convert-CMSchedule -ScheduleString $ScheduleString

    if (-not [string]::IsNullOrEmpty($schedule.DaySpan))
    {
        if ($schedule.DaySpan -gt 0)
        {
            $rInterval = 'Days'
            $rCount = $schedule.DaySpan
        }
        elseif ($schedule.HourSpan -gt 0)
        {
            $rInterval = 'Hours'
            $rCount = $schedule.HourSpan
        }
        elseif ($schedule.MinuteSpan -gt 0)
        {
            $rInterval = 'Minutes'
            $rCount = $schedule.MinuteSpan
        }

        $scheduleCim = New-CimInstance -ClassName $CimClassName -Property @{
            RecurInterval = $rInterval
            RecurCount    = $rCount
        } -ClientOnly -Namespace 'root/microsoft/Windows/DesiredStateConfiguration'

        return $scheduleCim
    }
}

<#
    .SYNOPSIS
        Converts the boundaries to a CIM Instance.
 
    .PARAMETER InputObject
        Specifies the array of hashtables of boundary returns.
#>

function ConvertTo-CimBoundaries
{
    [CmdletBinding()]
    [OutputType([Microsoft.Management.Infrastructure.CimInstance[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyCollection()]
        [AllowNull()]
        [Object[]]
        $InputObject
    )

    $cimClassName = 'DSC_CMBoundaryGroupsBoundaries'
    $cimNamespace = 'root/microsoft/Windows/DesiredStateConfiguration'
    $cimCollection = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]'

    foreach ($customField in $InputObject)
    {
        $convertBoundary = switch ($customField.BoundaryType)
        {
            '0' { 'IPSubnet' }
            '1' { 'AdSite' }
            '2' { 'IPv6Prefix' }
            '3' { 'IPRange' }
            '4' { 'Vpn' }
        }

        $cimProperties = @{
            Value      = $customField.Value
            Type       = $convertBoundary
        }

        $cimCollection += (New-CimInstance -ClassName $cimClassName `
                        -Namespace $cimNamespace `
                        -Property $cimProperties `
                        -ClientOnly)
    }

    return $cimCollection
}

<#
    .SYNOPSIS
        Converts the boundaries input to a CIM Instance transforming
        the IPSubnet input to a network address.
 
    .PARAMETER InputObject
        Specifies the array of CIM Instances for the boundary input.
#>

function Convert-BoundariesIPSubnets
{
    [CmdletBinding()]
    [OutputType([Microsoft.Management.Infrastructure.CimInstance[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $InputObject
    )

    $cimClassName = 'MSFT_KeyPairs'
    $cimNamespace = 'root/microsoft/Windows/DesiredStateConfiguration'
    $bounds = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]'

    foreach ($item in $InputObject)
    {
        if ($item.Type -eq 'IPSubnet')
        {
            $splitValue = $item.Value.Split('/')
            $address = Convert-CidrToIP -IPAddress $splitValue[0] -Cidr $splitValue[1]

            $cimProperties = @{
                Value     = $address.NetworkAddress
                Type      = "IPSubnet"
            }

            $bounds += (New-CimInstance -ClassName $cimClassName `
                        -Namespace $cimNamespace `
                        -Property $cimProperties `
                        -ClientOnly)
        }
        else
        {
            $cimProperties = @{
                Value     = $item.Value
                Type      = $item.Type
            }

            $bounds += (New-CimInstance -ClassName $cimClassName `
                        -Namespace $cimNamespace `
                        -Property $cimProperties `
                        -ClientOnly)
        }
    }

    return $bounds
}

<#
    .SYNOPSIS
        Returns the boundary ID based on Value and Type of boundary specified.
 
    .PARAMETER Value
        Specifies the value of the boundary.
 
    .PARAMETER Type
        Specifies the type of boundary options are ADSite, IPSubnet, and IPRange.
#>

function Get-BoundaryInfo
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $Value,

        [Parameter(Mandatory = $true)]
        [ValidateSet('ADSite','IPSubnet','IPRange')]
        [String]
        $Type
    )

    $convertBoundaryBack = switch ($Type)
    {
        'IPSubnet' { '0' }
        'AdSite'   { '1' }
        'IPRange'  { '3' }
    }

    return (Get-CMBoundary | Where-Object -FilterScript { ($_.BoundaryType -eq $convertBoundaryBack) -and
            ($_.Value -eq $Value) }).BoundaryID
}

<#
    .SYNOPSIS
        Returns Interval and count from the CM Schedule.
 
    .PARAMETER ScheduleString
        Specifies the string value of a CM Schedule to convert.
#>

function ConvertTo-ScheduleInterval
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $ScheduleString
    )

    $schedule = Convert-CMSchedule -ScheduleString $ScheduleString
    $itemList = @('DaySpan','MinuteSpan','HourSpan')
    $recurInterval = 'None'

    foreach ($item in $itemList)
    {
        if ($schedule.$item -gt 0)
        {
            $recurInterval = $item.Replace('Span','s')
            $recurCount = $schedule.$item
        }
    }

    return @{
        Interval = $recurInterval
        Count    = $recurCount
    }
}

<#
    .SYNOPSIS
        Converts hashtable into a named Cim Instance.
 
    .PARAMETER HashTable
        Specifies the schedule string to convert.
 
    .PARAMETER ClassName
        Specifies the desired Cim Instance classname for the output.
#>

function ConvertTo-AnyCimInstance
{
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Hashtable,

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

    $property = @{}
    foreach ($item in $Hashtable.GetEnumerator())
    {
        $property += @{
            $item.Key = $item.Value
        }
    }

    New-CimInstance -ClassName $ClassName -Namespace 'root/microsoft/Windows/DesiredStateConfiguration' `
        -Property $property -ClientOnly
}

<#
    .SYNOPSIS
        Returns the boundary ID based on Value and Type of boundary specified.
 
    .PARAMETER Match
        Specifies an array of values to validate if missing or extra settings compared to current state.
 
    .PARAMETER Include
        Specifies an array of values to validate if missing from current state.
 
    .PARAMETER Exclude
        Specifies an array of values to validate if extra compared to current state.
 
    .PARAMETER CurrentState
        Specifies an array to compare against for match, include, or exclude.
#>

function Compare-MultipleCompares
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [AllowEmptyString()]
        [String[]]
        $Match,

        [Parameter()]
        [AllowEmptyString()]
        [String[]]
        $Include,

        [Parameter()]
        [AllowEmptyString()]
        [String[]]
        $Exclude,

        [Parameter()]
        [String[]]
        $CurrentState
    )

    $missing = @()
    $remove = @()

    if (-not [string]::IsNullOrEmpty($Match))
    {
        $type = 'Match'

        if ($null -eq $CurrentState)
        {
            $missing = $Match
        }
        else
        {
            $compares = Compare-Object -ReferenceObject $Match -DifferenceObject $CurrentState

            foreach ($compare in $compares)
            {
                if ($compare.SideIndicator -eq '<=')
                {
                    $missing += $compare.InputObject
                }
                else
                {
                    $remove += $compare.InputObject
                }
            }
        }
    }
    else
    {
        if (-not [string]::IsNullOrEmpty($Include))
        {
            $type = 'Include'

            foreach ($item in $Include)
            {
               if ($CurrentState -notcontains $item)
               {
                    $missing += $item
               }
            }
        }

        if (-not [string]::IsNullOrEmpty($Exclude))
        {
            if ($type -eq 'Include')
            {
                $type = 'Include, Exclude'
            }
            else
            {
                $type = 'Exclude'
            }

            foreach ($item in $Exclude)
            {
                if ($CurrentState -contains $item)
                {
                    $remove += ($CurrentState | Where-Object -FilterScript {$_ -eq $item})
                }
            }
        }
    }

    return @{
        Type         = $type
        Missing      = $missing
        Remove       = $remove
        CurrentState = $CurrentState
    }
}

<#
    .SYNOPSIS
        Adds the Distribution Point to the Distribution Point Group.
 
    .PARAMETER DistributionPointName
        Specifies the Distribution Point to modify Distribution Point Group membership.
 
    .PARAMETER DistributionPointGroupName
        Specifies a Distribution Group to add to the Distribution Point.
#>

function Add-DPToDPGroup
{
    [OutputType([System.Boolean])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $DistributionPointName,

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

    $count = 0
    $success = $false

    do
    {
        try
        {
            Write-Verbose -Message ($script:localizedData.AddDP -f $DistributionPointName, $DistributionPointGroupName) -Verbose
            Add-CMDistributionPointToGroup -DistributionPointName $DistributionPointName -DistributionPointGroupName $DistributionPointGroupName
            $success = $true
            $count = 12
        }
        catch
        {
            Write-Warning -Message ($script:localizedData.Wait -f $DistributionPointName) -Verbose
            Start-Sleep -Seconds 10
            $count ++
        }
    }
    until ($count -eq 12)

    return $success
}

<#
    .SYNOPSIS
        Converts CMSchedule string. This will return a hashtable of results.
 
    .PARAMETER ScheduleString
        Specifies the schedule string.
#>

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

    $schedule = Convert-CMSchedule -ScheduleString $ScheduleString

    if ($schedule)
    {
        if ($schedule.StartTime.Minute.ToString().Length -eq 1)
        {
            $minTime = "0" + $($schedule.StartTime.Minute)
        }
        else
        {
            $minTime = $($schedule.StartTime.Minute)
        }

        if ($schedule.StartTime.Hour.ToString().Length -eq 1)
        {
            $hourTime = "0" + $($schedule.StartTime.Hour)
        }
        else
        {
            $hourTime = $($schedule.StartTime.Hour)
        }

        $startTime = "$($hourTime):$($minTime)"
        $startDate = $schedule.StartTime.Date.ToShortDateString()

        if ($schedule.Day)
        {
            $day = @('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday')[$schedule.Day - 1]
        }

        $recurSetting = $schedule.SmsProviderObjectPath
        if ($recurSetting -match 'RecurMonthly')
        {
            $interval = $schedule.ForNumberofMonths

            if ($recurSetting -match 'ByDate')
            {
                $sched = 'MonthlyByDay'
                $daySpan = $schedule.MonthDay
            }
            elseif ($recurSetting -match 'ByWeekday')
            {
                $sched = 'MonthlyByWeek'
                $numberofweeks = @('Last','First','Second','Third','Fourth')[$schedule.WeekOrder]
                $dayofWeek = $day
            }
        }
        elseif ($recurSetting -match 'RecurWeekly')
        {
            $sched = 'Weekly'
            $interval = $schedule.ForNumberOfWeeks
            $dayOfWeek = $day
        }
        elseif ($recurSetting -match 'RecurInterval')
        {
            if ($schedule.DaySpan)
            {
                $sched = 'Days'
                $interval = $schedule.DaySpan
            }
            elseif ($schedule.HourSpan)
            {
                $sched = 'Hours'
                $interval = $schedule.HourSpan
            }
            elseif ($schedule.MinuteSpan)
            {
                $sched = 'Minutes'
                $interval = $schedule.MinuteSpan
            }
        }
        elseif ($recurSetting -match 'NonRecurring')
        {
            $sched = 'None'
        }

        if ($schedule.DayDuration -gt 0)
        {
            $dayDur = $schedule.DayDuration
        }

        if ($schedule.HourDuration -gt 0)
        {
            $hourDur = $schedule.HourDuration
        }

        if ($schedule.MinuteDuration -gt 0)
        {
            $minDur += $schedule.MinuteDuration
        }
    }

    return @{
        MonthDay       = $daySpan
        ScheduleType   = $sched
        RecurInterval  = $interval
        DayOfWeek      = $day
        Start          = "$startDate $startTime"
        WeekOrder      = $numberofweeks
        DayDuration    = $dayDur
        HourDuration   = $hourDur
        MinuteDuration = $minDur
    }
}

<#
    .SYNOPSIS
        This will test the desired state.
 
    .PARAMETER Start
        Specifies the start date and start time for the schedule Month/Day/Year, example 1/1/2020 02:00.
 
    .PARAMETER ScheduleType
        Specifies the schedule type.
 
    .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.
 
    .PARAMETER HourDuration
        Specifies the duration for the schedule in hours, max value 23.
 
    .PARAMETER MinuteDuration
        Specifies the duration for the schedule in minutes, max value 59.
 
    .PARAMETER State
        Specifies the currently applied schedule.
#>

function Test-CMSchedule
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateSet('MonthlyByWeek','MonthlyByDay','Weekly','Days','Hours','Minutes','None')]
        [String]
        $ScheduleType,

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

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

        [Parameter()]
        [UInt32]
        $RecurInterval,

        [Parameter()]
        [String]
        $Start,

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

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

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

        [Parameter()]
        [HashTable]
        $State
    )

    $variablesToCheck = @('ScheduleType')
    $result = $true

    if ($PSBoundParameters.ContainsKey('HourDuration'))
    {
        $variablesToCheck += @('HourDuration')
    }

    if ($PSBoundParameters.ContainsKey('MinuteDuration'))
    {
        $variablesToCheck += @('MinuteDuration')
    }

    if ($PSBoundParameters.ContainsKey('Start'))
    {
        $startTime = [string]$Start -as [DateTime]

        if ([string]::IsNullOrEmpty($startTime))
        {
            Write-Warning -Message ($script:localizedData.StartFormat -f $Start)
            $result = $false
        }
        else
        {
            $variablesToCheck += 'Start'
        }
    }

    if ($ScheduleType -ne 'None' -and -not $PSBoundParameters.ContainsKey('RecurInterval'))
    {
        Write-Warning -Message $script:localizedData.MissingInterval
        $result = $false
    }
    else
    {
        if ($ScheduleType -match 'Monthly')
        {
            if ($RecurInterval -ge 13)
            {
                Write-Warning -Message ($script:localizedData.MaxIntervalMon -f $RecurInterval)
                $PSBoundParameters.Remove('RecurInterval') | Out-Null
                $PSBoundParameters.Add('RecurInterval',12)
            }

            if ($ScheduleType -eq 'MonthlyByWeek')
            {
                if ($PSBoundParameters.ContainsKey('MonthlyWeekOrder') -and $PSBoundParameters.ContainsKey('DayOfWeek'))
                {
                    $variablesToCheck += @('MonthlyWeekOrder','DayOfWeek','RecurInterval')
                }
                else
                {
                    Write-Warning -Message $script:localizedData.MonthlyByWeek
                    $result = $false
                }
            }
            elseif ($scheduleType -eq 'MonthlyByDay')
            {
                if ($PSBoundParameters.ContainsKey('DayOfMonth'))
                {
                    $variablesToCheck += @('DayOfMonth','RecurInterval')
                }
                else
                {
                    Write-warning -Message $script:localizedData.MonthlyByDay
                    $result = $false
                }
            }
        }
        elseif ($ScheduleType -eq 'Weekly')
        {
            if ($RecurInterval -ge 5)
            {
                Write-Warning -Message ($script:localizedData.MaxIntervalWeek -f $RecurInterval)
                $PSBoundParameters.Remove('RecurInterval') | Out-Null
                $PSBoundParameters.Add('RecurInterval',4)
            }

            if (-not [string]::IsNullOrEmpty($DayOfWeek))
            {
                $variablesToCheck += @('DayOfWeek','RecurInterval')
            }
            else
            {
                Write-warning -Message $script:localizedData.Weekly
                $result = $false
            }
        }
        elseif ($ScheduleType -eq 'Days')
        {
            if ($RecurInterval)
            {
                if ($RecurInterval -ge 32)
                {
                    Write-Warning -Message ($script:localizedData.MaxIntervalDays -f $RecurInterval)
                    $PSBoundParameters.Remove('RecurInterval') | Out-Null
                    $PSBoundParameters.Add('RecurInterval',31)
                }

                $variablesToCheck += @('RecurInterval')
            }
        }
        elseif ($ScheduleType -eq 'Hours')
        {
            if ($RecurInterval)
            {
                if ($RecurInterval -ge 24)
                {
                    Write-Warning -Message ($script:localizedData.MaxIntervalHours -f $RecurInterval)
                    $PSBoundParameters.Remove('RecurInterval') | Out-Null
                    $PSBoundParameters.Add('RecurInterval',23)
                }

                $variablesToCheck += @('RecurInterval')
            }
        }
        elseif ($ScheduleType -eq 'Minutes')
        {
            if ($RecurInterval)
            {
                if ($RecurInterval -ge 60)
                {
                    Write-Warning -Message ($script:localizedData.MaxIntervalMins -f $RecurInterval)
                    $PSBoundParameters.Remove('RecurInterval') | Out-Null
                    $PSBoundParameters.Add('RecurInterval',59)
                }
                elseif ($RecurInterval -le 4)
                {
                    Write-Warning -Message ($script:localizedData.MinIntervalMins -f $MinuteDuration)
                    $PSBoundParameters.Remove('RecurInterval') | Out-Null
                    $PSBoundParameters.Add('RecurInterval',5)
                }

                $variablesToCheck += @('RecurInterval')
            }
        }

        $testParams = @{
            CurrentValues = $State
            DesiredValues = $PSBoundParameters
            ValuesToCheck = $variablesToCheck
        }

        $returnState = Test-DscParameterState @testParams -Verbose -TurnOffTypeChecking

        foreach ($item in $PSBoundParameters.Keys)
        {
            if ($variablesToCheck -notcontains $item -and $item -ne 'State')
            {
                Write-Warning -Message ($script:localizedData.ExtraSettings -f $item, $ScheduleType)
            }
        }
    }

    if ($returnState -eq $true -and $result -eq $true)
    {
        return $true
    }
    else
    {
        return $false
    }
}

<#
    .SYNOPSIS
        This will convert the PSBoundParameters into a hashtable to be used to create a Config Mgr. schedule.
 
    .PARAMETER ScheduleType
        Specifies the schedule type for the schedule.
 
    .PARAMETER Start
        Specifies the start date and start time for the schedule Month/Day/Year, example 1/1/2020 02:00.
 
    .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.
 
    .PARAMETER HourDuration
        Specifies the duration for the schedule in hours, max value 23.
 
    .PARAMETER MinuteDuration
        Specifies the duration for the schedule in minutes, max value 59.
 
#>

function Set-CMSchedule
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateSet('MonthlyByWeek','MonthlyByDay','Weekly','Days','Hours','Minutes','None')]
        [String]
        $ScheduleType,

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

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

        [Parameter()]
        [UInt32]
        $RecurInterval,

        [Parameter()]
        [String]
        $Start,

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

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

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

    if ($scheduleType -ne 'None' -and -not $PSBoundParameters.ContainsKey('RecurInterval'))
    {
        throw $script:localizedData.MissingInterval
    }

    if ($Start)
    {
        $startTime = [string]$Start -as [DateTime]

        if ([string]::IsNullOrEmpty($startTime))
        {
            throw ($script:localizedData.StartFormat -f $Start)
        }

        $params = @{
            Start = $startTime
        }
    }

    if ($HourDuration)
    {
        $params += @{
            DurationInterval = 'Hours'
            DurationCount    = $HourDuration
        }
    }

    if ($MinuteDuration)
    {
        $params += @{
            DurationInterval = 'Minutes'
            DurationCount    = $MinuteDuration
        }
    }

    if ($ScheduleType -Match 'Monthly')
    {
        if ($RecurInterval -ge 13)
        {
            Write-Warning -Message ($script:localizedData.MaxIntervalMon -f $RecurInterval)
            $count = 12
        }
        else
        {
            $count = $RecurInterval
        }

        $params += @{
            RecurCount = $count
        }

        if ($ScheduleType -eq 'MonthlyByWeek')
        {
            if (-not $PSBoundParameters.ContainsKey('MonthlyWeekOrder') -or -not $PSBoundParameters.ContainsKey('DayOfWeek'))
            {
                throw $script:localizedData.MonthlyByWeek
            }

            $params += @{
                WeekOrder = $MonthlyWeekOrder
                DayOfWeek = $DayOfWeek
            }
        }
        elseif ($ScheduleType -eq 'MonthlyByDay')
        {
            if (-not $PSBoundParameters.ContainsKey('DayOfMonth'))
            {
                throw $script:localizedData.MonthlyByDay
            }

            $params += @{
                DayOfMonth = $DayOfMonth
            }
        }
    }
    elseif ($ScheduleType -eq 'Weekly')
    {
        if ($RecurInterval -gt 4)
        {
            Write-Warning -Message ($script:localizedData.MaxIntervalWeek -f $RecurInterval)
            $count = 4
        }
        else
        {
            $count = $RecurInterval
        }

        if ([string]::IsNullOrEmpty($DayOfWeek))
        {
            throw $script:localizedData.Weekly
        }

        $params += @{
            DayOfWeek  = $DayOfWeek
            RecurCount = $count
        }
    }
    elseif ($ScheduleType -eq 'None')
    {
        $params += @{
            Nonrecurring = $null
        }
    }
    else
    {
        if ($ScheduleType -eq 'Days' -and $RecurInterval -ge 32)
        {
            Write-Warning -Message ($script:localizedData.MaxIntervalDays -f $RecurInterval)
            $count = 31
        }
        elseif ($ScheduleType -eq 'Hours' -and $RecurInterval -ge 24)
        {
            Write-Warning -Message ($script:localizedData.MaxIntervalHours -f $RecurInterval)
            $count = 23
        }
        elseif (($ScheduleType -eq 'Minutes') -and ($RecurInterval -ge 60 -or $RecurInterval -le 4))
        {
            if ($RecurInterval -ge 60)
            {
                Write-Warning -Message ($script:localizedData.MaxIntervalMins -f $RecurInterval)
                $count = 59
            }
            else
            {
                Write-Warning -Message ($script:localizedData.MinIntervalMins -f $RecurInterval)
                $count = 5
            }
        }
        else
        {
            $count = $RecurInterval
        }

        $params += @{
            RecurInterval = $ScheduleType
            RecurCount    = $count
        }
    }

    return $params
}

Export-ModuleMember -Function @(
    'Import-ConfigMgrPowerShellModule'
    'Convert-CidrToIP'
    'ConvertTo-CimCMScheduleString'
    'ConvertTo-CimBoundaries'
    'Convert-BoundariesIPSubnets'
    'Get-BoundaryInfo'
    'ConvertTo-ScheduleInterval'
    'ConvertTo-AnyCimInstance'
    'Compare-MultipleCompares'
    'Add-DPToDPGroup'
    'Get-CMSchedule'
    'Test-CMSchedule'
    'Set-CMSchedule'
)