functions/SetJobSchedules.ps1

Function Set-JobSchedules {
    <#
.Synopsis
Create or modify SQL Agent Job Schedules.
.Description
SQL Agent Job Schedule will be created or updated to match the settings in the xml file.
.Parameter sqlServer
The SQL Connection that SQL Agent Job Schedule is on/will be created on.
.Parameter root
The XML Object
.Example
$SqlConnectionString = "data source = .; initial catalog = master; trusted_connection = true;"
$JobManifestXmlFile = "C:\Reports\Our_First_Job.xml"
$SqlConnection = Connect-SqlConnection -ConnectionString $SqlConnectionString
[xml] $_xml = [xml] (Get-Content -Path $JobManifestXmlFile)
$x = Get-Xml -XmlFile $_xml
$sqlAgentJob = Set-Job -SqlServer $SqlConnection -root $x
Set-JobSchedules -SqlServer $SqlConnection -root $x -job $SqlAgentJob
Disconnect-SqlConnection -SqlDisconnect $SqlConnection
#>

    [CmdletBinding()]
    param
    (
        [Microsoft.SqlServer.Management.Smo.SqlSmoObject]
        [ValidateNotNullorEmpty()]
        $SqlServer,
        [System.Xml.XmlLinkedNode]
        [ValidateNotNullorEmpty()]
        $root,
        [Microsoft.SqlServer.Management.Smo.Agent.AgentObjectBase]
        [ValidateNotNullorEmpty()]
        $job
    )
    
    [string]$JobName = $Job.Name
    $schedules = $root.Schedules
    [System.Xml.XmlElement] $schedule = $null
    $domain = [Environment]::UserDomainName
    $uname = [Environment]::UserName
    [string]$whoAmI = "$domain\$uname"
    $ServerResults = @{}
    try {
        $db = New-Object Microsoft.SqlServer.Management.Smo.Database
        $db = $SqlServer.Databases.Item("msdb")
        $ds = $db.ExecuteWithResults("select s.schedule_id, s.name from sysjobs j
    inner join sysjobschedules js on js.job_id = j.job_id
    inner join sysschedules s on s.schedule_id = js.schedule_id
    where j.name = '"
 + $jobName + "'") 
        $t = $ds.Tables[0]
        Foreach ($row in $t.Rows) {
            $ServerResults.Add($row.name, $row.schedule_id)
        }
    }
    catch {
        throw $_.Exception
    }
    if ($ServerResults.Count -gt 0) {
        $msg = "Dropping all schedules for job $jobName that do not exist in XML..."
        Write-Verbose $msg -Verbose
        $ds = $db.ExecuteWithResults("SELECT IS_SRVROLEMEMBER('sysadmin') as 'AmISysAdmin';")
        $AmISysAdmin = $ds.Tables[0].Rows[0]."AmISysAdmin"
        if ($AmISysAdmin -eq 1) {
            Write-Verbose "User $whoAmI is sysadmin on instance, so job schedule can be dropped irrespetive of owner." -Verbose
        }
        if ($AmISysAdmin -eq 0){
            Write-Verbose "User $whoAmI not sysadmin, so need to check that they are owner of job schedules, otherwise schedules not owned by user cannot be dropped."
            $ds = $db.ExecuteWithResults("SELECT SUSER_SID() AS SID;")
            [String]$CurrentUserSid = $ds.Tables[0].Rows[0]."SID"
        }
        foreach ($ServerSchedule in $ServerResults.Keys) {
            if ($schedules.schedule.name -notcontains $ServerSchedule) {
                try {
                    if ($AmISysAdmin -eq 0)
                    {
                        $ds = $db.ExecuteWithResults("select owner_sid from sysschedules syssch where syssch.name = '$ServerSchedule'")
                        [string]$JobScheduleOwnerSid = $ds.Tables[0].Rows[0]."owner_sid"
                        if(@(Compare-Object $CurrentUserSid $JobScheduleOwnerSid -SyncWindow 0).Length -gt 0)
                        {
                            Write-Error "User $whoAmI is not owner of Schedule $ServerSchedule. Either alter or set user executing PowerShell to sysadmin!"
                            Throw
                        }
                    }
                    Write-Verbose "SQL Statement executed to drop schedule:" -Verbose
                    Write-Verbose "EXEC dbo.sp_delete_schedule @schedule_id = '$($ServerResults.Get_Item($ServerSchedule)) ',@force_delete = 1;" -Verbose
                    $db.ExecuteNonQuery("EXEC dbo.sp_delete_schedule
        @schedule_id = '"
 + $($ServerResults.Get_Item($ServerSchedule)) + "',
        @force_delete = 1;"
)
                    $msg = "Schedule $($ServerSchedule) on job $jobName deleted..."
                    Write-Verbose $msg -Verbose 
                }
                catch {
                    throw $_.Exception
                }
            }
        }
    }
    
    foreach ($schedule in $schedules.ChildNodes) {
        #name of schedule
        [string]$schedule_name = $schedule.Name
        #schedule child nodes
        if ($schedule.Enabled) {
            [bool]$schedule_enabled = if ($schedule.Enabled -eq "True") {$True} else {$false}
        }
        else {
            [bool]$schedule_enabled = $false
        }
        [string]$Schedule_startDateString = $schedule.StartDate
        [string]$Schedule_EndDateString = $schedule.EndDate
        #frequency child nodes
        [string[]]$schedule_FrequencyInterval = $schedule.Frequency.Interval
        [string]$schedule_FrequencyRecurrs = $schedule.Frequency.Recurrs
        [string]$schedule_frequencyType = $schedule.Frequency.Type
        #daily frequency child nodes
        [string]$Schedule_DailyFrequencyEvery = $schedule.DailyFrequency.Every
        [string]$Schedule_DailyFrequencyInterval = $schedule.DailyFrequency.Interval
        [string]$Schedule_DailyFrequencyStartTimeHour = $schedule.DailyFrequency.StartHour 
        [string]$Schedule_DailyFrequencyStartTimeMinute = $schedule.DailyFrequency.StartMinute
        [string]$Schedule_DailyFrequencyStartTimeSecond = $schedule.DailyFrequency.StartSecond
        [string]$Schedule_DailyFrequencyEndTimeHour = $schedule.DailyFrequency.EndHour 
        [string]$Schedule_DailyFrequencyEndTimeMinute = $schedule.DailyFrequency.EndMinute
        [string]$Schedule_DailyFrequencyEndTimeSecond = $schedule.DailyFrequency.EndSecond 
        try {
            $js = new-object ('Microsoft.SqlServer.Management.Smo.Agent.JobSchedule') ($job, $schedule_name)
            if ($ServerResults.Keys -notcontains $schedule_name){
                $create = $true
            }
            else {
                $create = $false
                $js = $job.JobSchedules | Where-Object {$_.Name -eq $schedule_name}
                $js.Refresh()
            }
        }
        catch {
            throw $_.Exception
        }
        #formatting frequency type or assigning it a numeric value if it is something like "Monday, Wednesday" or "Weekday".
        if (![String]::IsNullOrWhiteSpace($schedule_FrequencyInterval)) {
            if ($schedule_frequencyType -eq "Weekly") {
                [int]$FrequencyInterVal = Get-FrequencyIntervalValue $schedule_FrequencyInterval
            }
            else {
                [int]$FrequencyInterVal = [convert]::ToInt32($schedule_FrequencyInterval, 10)
            }
        }
        try {
            $js.IsEnabled = $schedule_enabled
            #"if" statements are used to verify string is not empty in those cases where an enpty string will cause a "create job schedule" failure.
            if (![string]::IsNullOrEmpty($Schedule_startDateString)) {
                $js.ActiveStartDate = [DateTime]$Schedule_startDateString
            }
            if (![String]::IsNullOrEmpty($Schedule_EndDateString)) {
                $js.ActiveEndDate = [DateTime]$Schedule_EndDateString
            }
            if (![String]::IsNullOrEmpty($FrequencyInterVal)) {
                $js.FrequencyInterval = $FrequencyInterVal
            }
            if (![String]::IsNullOrEmpty($schedule_FrequencyRecurrs)) {
                $js.FrequencyRecurrenceFactor = $schedule_FrequencyRecurrs
            }
            if (![String]::IsNullOrEmpty($schedule_frequencyType)) {
                $js.FrequencyTypes = $schedule_frequencyType
            }
            if (![String]::IsNullOrEmpty($Schedule_DailyFrequencyEvery)) {
                $js.FrequencySubDayTypes = $Schedule_DailyFrequencyEvery
            }
            if (![String]::IsNullOrEmpty($Schedule_DailyFrequencyInterval)) {
                $js.FrequencySubDayInterval = [convert]::ToInt32($Schedule_DailyFrequencyInterval, 10)
            }
            if (![String]::IsNullOrEmpty($Schedule_DailyFrequencyStartTimeHour)) {
                $StartTimeSpan = New-TimeSpan -Hours ([convert]::ToInt32($Schedule_DailyFrequencyStartTimeHour , 10)) -Minutes ([convert]::ToInt32($Schedule_DailyFrequencyStartTimeMinute , 10)) -Seconds ([convert]::ToInt32($Schedule_DailyFrequencyStartTimeSecond , 10))
                $js.ActiveStartTimeOfDay = $StartTimeSpan
            }
            if (![string]::IsNullOrEmpty($Schedule_DailyFrequencyEndTimeHour)) {
                $EndTimeSpan = New-TimeSpan -Hours ([convert]::ToInt32($Schedule_DailyFrequencyEndTimeHour , 10)) -Minutes ([convert]::ToInt32($Schedule_DailyFrequencyEndTimeMinute , 10)) -Seconds ([convert]::ToInt32($Schedule_DailyFrequencyEndTimeSecond , 10))
                $js.ActiveEndTimeOfDay = $EndTimeSpan
            }
            if ($create) {
                try {
                    $js.Create()
                    Write-Verbose "Job Schedule $schedule_name created successfully." -Verbose
                    $create = $false
                    Remove-Variable -Name js
                }
                catch {
                    throw $_.Exception
                }
            }
            else {
                $js.Alter()
                Remove-Variable -Name js
                Write-Verbose "Job Schedule $schedule_name properties updated successfully." -Verbose
            }
        }
        catch {
            throw $_.Exception
        }
    }
}