param( [switch]$debug, [switch]$clear, [switch]$noplan ) if ($debug) { $script:debug = $debug } if($noplan) { $script:noplan = $noplan } $teamsName = "IT Support & Delivery" $scheduleGroupName = "IT SUPPORT" $ClientId = "6cb76bbf-f253-4551-87a8-5e1f49d8ace3" $ClientSecret = "IQg8Q~JTBTDbrHyjKMc.KMSETtQoKWvUhnCr1aY1" $tenantid = "e81b4a31-e8ad-4df6-aa30-3bdcc1da2cdd" $userIdAdmin = "747bd624-5cd7-4ae0-952f-5dc374e07a5e" $logPath = (".\log_{0}.log" -f (Get-Date).toString("yyyy_MM_dd")) function Write-Debug { param( [string]$message ) if ($script:debug) { Write-Host $message } else { $message | Add-Content $logPath } } function Set-Shift { param( [string]$userId, [string]$groupID, [string]$shiftName, [System.DateTime]$StartDate, [System.DateTime]$EndDate, [string]$color, [string]$teamID, [string]$mail ) $params = @{ UserId = $userId schedulingGroupId = $groupID sharedShift = @{ notes = ($shiftName) startDateTime = [System.DateTime]::Parse($StartDate.ToString("yyyy-MM-dd'T'HH:mm:ssZ")) endDateTime = [System.DateTime]::Parse($EndDate.ToString("yyyy-MM-dd'T'HH:mm:ssZ")) theme = $color } } if($script:noplan -eq $false) { $newShift = New-MgTeamScheduleShift -TeamId $teamID -BodyParameter $params -Headers @{ "MS-APP-ACTS-AS" = $userIdAdmin } Write-Debug("{0} {1}: setting {2}'s shift - {3} {4}-{5}" -f $dayDate, $dayDate.DayOfWeek, $mail, $shiftName, $StartDate, $EndDate) } else { Write-Debug("{0} {1}: Not setting {2}'s shift - {3} {4}-{5} - running with -noplan" -f $dayDate, $dayDate.DayOfWeek, $mail, $shiftName, $StartDate, $EndDate) $newshift = 0 } return $newShift } function Remove-Shift { param( [PSCustomObject]$shift, [string]$teamID ) if($script:noplan -eq $false) { Write-Debug("{0} {1}: removing {2}'s shift - {2} {3}-{4}" -f $dayDate, $dayDate.DayOfWeek, $futureds_email, $shift.Notes, $shift.StartDateTime, $shift.EndDateTime) Remove-MgTeamScheduleShift -ShiftId $shift.id -TeamId $teamID } else { Write-Debug("{0} {1}: Not removing {2}'s shift - {2} {3}-{4} - running with -noplan" -f $dayDate, $dayDate.DayOfWeek, $futureds_email, $shift.Notes, $shift.StartDateTime, $shift.EndDateTime) } } function Invoke-HasTimeOff { param( [string]$UID, [PSCustomObject]$timeoff, [string]$mail ) foreach ($toff in $timeoff) { if($toff.UserId -eq $UID) { Write-Debug("{0} {1}: {2} has time off" -f $dayDate, $dayDate.DayOfWeek, $mail) return $true break } } return $false } function Invoke-HasShift { param( [string]$UID, [PSCustomObject]$shifts, [string]$mail ) foreach ($shift in $shifts) { if($shift.UserId -eq $UID) { Write-Debug("{0} {1}: {2} has a shift already - {3} {4}-{5}" -f $dayDate, $dayDate.DayOfWeek, $mail, $shift.SharedShift.Notes, $shift.SharedShift.StartDateTime, $shift.SharedShift.EndDateTime) return $true break } } return $false } function Invoke-HasShiftorTimeOff { param( [string]$UID, [PSCustomObject]$shifts, [PSCustomObject]$timeoff, [string]$mail ) foreach ($shift in $shifts) { if($shift.UserId -eq $UID) { Write-Debug("{0} {1}: {2} has a shift already - {3} {4}-{5}" -f $dayDate, $dayDate.DayOfWeek, $mail, $shift.SharedShift.Notes, $shift.SharedShift.StartDateTime, $shift.SharedShift.EndDateTime) return $true break } } foreach ($toff in $timeoff) { if($toff.UserId -eq $UID) { Write-Debug("{0} {1}: {2} has time off" -f $dayDate, $dayDate.DayOfWeek, $mail) return $true break } } return $false } $body = @{ grant_type = "client_credentials" resource = "https://graph.microsoft.com" client_id = $ClientId client_secret = $ClientSecret } $authUri = "https://login.microsoftonline.com/$tenantid/oauth2/token" $response = Invoke-RestMethod $authUri -Method 'POST' -Body $body $token = $response.access_token Import-Module Microsoft.Graph.Teams Connect-MgGraph -AccessToken $($token | ConvertTo-SecureString -AsPlainText -Force) >> $null # ---| SCHEDULE |--- $schedule = Get-Content -Path "./config.json" -Raw | ConvertFrom-Json # ---| ALL MEMBERS SPECIFIED IN THE SCHEDULE |--- $allemails = $schedule.PSObject.Properties | ForEach-Object { if ($_.Name -in @("monday", "tuesday", "wednesday", "thursday", "friday")) { $_.Value.PSObject.Properties | ForEach-Object { if ($_.Name -in @("ds", "ho", "os", "ns")) { $_.Value | ForEach-Object { $_ } } } } } | Select-Object -Unique # --------------------------- | DATE SETTINGS | --------------------------- # ---| Today's date |--- $today = (Get-Date).Date # ---| Specific date |--- #$today = Get-Date -Day 27 -Month 10 -Year 2025 -Hour 9 -Minute 00 -Second 00 -Millisecond 00 $daysahead = 14 #How many days ahead to plan $timeoff_past_look = -7 #How many days to look into the past for the start of a multi-day time off # ---| Time span |--- $startSpanDate = Get-Date -Day $today.Day -Month $today.Month -Year $today.Year -Hour 9 -Minute 00 -Second 00 -Millisecond 00 $endSpanDate = Get-Date -Day $startSpanDate.AddDays($daysahead).Day -Month $startSpanDate.AddDays($daysahead).Month -Year $startSpanDate.AddDays($daysahead).Year -Hour 17 -Minute 00 -Second 00 -Millisecond 00 # --------------------------- | RLCZ MAINTENANCE SETTINGS | --------------------------- $rlcz_mtnc_email = "jiri.kotlan@itego.cz" $rlcz_shift_reduction = -3 #How many hours does the next day shift get reduced by # --------------------------- | MAIN SCRIPT | --------------------------- try { $team = Get-MgTeam -Property "id,displayName" | Where-Object -property displayName -value $teamsName -eq $group = Get-MgTeamScheduleSchedulingGroup -TeamId $team.id -Headers @{ "MS-APP-ACTS-AS" = $userIdAdmin } | Where-Object -property displayName -value $scheduleGroupName -eq $allshifts = [object[]] (Get-MgTeamScheduleShift -TeamId $team.id -Filter "SharedShift/StartDateTime ge $($startSpanDate.toString('yyyy-MM-ddT00:00:00Z'))" -All -Headers @{ "MS-APP-ACTS-AS" = $userIdAdmin }) $timeoff_all = [object[]] (Get-MgTeamScheduleTimeOff -TeamId $team.id -Filter "SharedTimeOff/StartDateTime ge $($startSpanDate.AddDays($timeoff_past_look).toString('yyyy-MM-ddT00:00:00Z'))" -All -Headers @{ "MS-APP-ACTS-AS" = $userIdAdmin } | Select-Object -Property *,@{name="Date";expression={$_.SharedTimeOff.StartDateTime.AddHours(1).Date}}) # ---| SHIFT CLEARING with -clear |--- if ($clear) { foreach ($shift in $allshifts) { if($shift.SharedShift.Notes -ne "RLCZ") { Remove-Shift -shift $shift -teamID $team.Id } } return } $timeSpan = New-TimeSpan -Start $startSpanDate -End $endSpanDate Write-Debug("Planning from {0} to {1}" -f $startSpanDate, $endSpanDate) # ---| DAY LOOP |--- for ($dayNumber = 0; $dayNumber -lt $timespan.Days; $dayNumber++) { $dayDate = $today.AddDays($dayNumber); # ---| WEEKEND CHECK |--- if($dayDate.DayOfWeek -in @("Saturday", "Sunday")) { Write-Debug("{0} {1}: skipping weekend day" -f $dayDate, $dayDate.DayOfWeek) continue } # ---| NEW DAY INIT |--- $ds_shift_inplace = $false $ho_emails = $schedule.($dayDate.DayOfWeek.ToString().ToLower()).'ho' $ds_email = $schedule.($dayDate.DayOfWeek.ToString().ToLower()).'ds' $os_emails = $schedule.($dayDate.DayOfWeek.ToString().ToLower()).'os' $ns_emails = $schedule.($dayDate.DayOfWeek.ToString().ToLower()).'ns' # ---| TIME SYNC |--- $dateStart = $(get-date -Day $dayDate.Day -Month $dayDate.Month -Year $dayDate.Year -Hour 9 -Minute 00 -Second 00).AddHours(-1) $dateEnd = $(get-date -Day $dayDate.Day -Month $dayDate.Month -Year $dayDate.Year -Hour 17 -Minute 00 -Second 00).AddHours(-1) if (($dayDate.IsDaylightSavingTime()) -eq $true) { $dateStart = $dateStart.AddHours(-1) $dateEnd = $dateEnd.AddHours(-1) } $dayEnd = Get-Date -Day $dayDate.Day -Month $dayDate.Month -Year $dayDate.Year -Hour 23 -Minute 00 -Second 0 $dayStart = $dayEnd.AddDays(-1) # ---| TODAY'S SHIFTS AND TIME OFF |--- $shifts_today = [Object[]] $allshifts | Where-Object -Filter { $_.schedulingGroupId -eq $group.Id -and $_.SharedShift.StartDateTime.ToString("yyyy-MM-dd HH:mm") -ge $dateStart.ToString("yyyy-MM-dd HH:mm") -and $_.SharedShift.EndDateTime.ToString("yyyy-MM-dd HH:mm") -le $dateEnd.ToString("yyyy-MM-dd HH:mm") } $timeoff_today = [Object[]] $timeoff_all | Where-Object { $_.SharedTimeOff.StartDateTime.Date -le $dayStart.Date -and $_.SharedTimeOff.EndDateTime.Date -ge $dayEnd.Date } $manual_dayshifts = [Object[]] $shifts_today | Where-Object -Filter { ($_.SharedShift.Notes -eq "Home Office - Denní směna") -or ($_.SharedShift.Notes -eq "Office - Denní směna") } if ($null -eq $manual_dayshifts) { $ds_shift_inplace = $false Write-Debug("{0} {1}: day shift not yet set" -f $dayDate, $dayDate.DayOfWeek) } else { $ds_shift_inplace = $true Write-Debug("{0} {1}: day shift set already" -f $dayDate, $dayDate.DayOfWeek) } # ---| TEAM MEMBER LOOP |--- foreach($email in $allemails) { $userId = $(Get-MgUser -Filter "UserPrincipalName eq '$email' or proxyAddresses/any(c:c eq 'smtp:$email')").Id # ---| DAY SHIFT CHECK |--- if($ds_shift_inplace -eq $false) { if($email -eq $ds_email) { Write-Debug("{0} {1}: checking {2} for day shift availability..." -f $dayDate, $dayDate.DayOfWeek, $email) # ---| IF DAY SHIFT CAN BE ASSIGNED TO THE PROPER MEMEBER |--- if((Invoke-HasShiftorTimeOff -UID $userId -shifts $shifts_today -timeoff $timeoff_today -mail $email) -eq $false) { # ---| HOME-OFFICE DAY SHIFT |--- if ($email -in $ho_emails) { $newshift = Set-Shift -userId $userId -groupID $group.id -shiftName "Home Office - Denní směna" -StartDate $dateStart -EndDate $dateEnd -color "purple" -teamID $team.id -mail $email $allshifts += [Object[]] $newshift $ds_shift_inplace = $true continue } # ---| OFFICE DAY SHIFT |--- else { $newshift = Set-Shift -userId $userId -groupID $group.id -shiftName "Office - Denní směna" -StartDate $dateStart -EndDate $dateEnd -color "purple" -teamID $team.id -mail $email $allshifts += [Object[]] $newShift $ds_shift_inplace = $true continue } } # ---| DAY SHIFT REPLACEMENT CHECK |--- else { Write-Debug("{0} {1}: looking ahead for a day shift replacement" -f $dayDate, $dayDate.DayOfWeek) $remainingdays = $timespan.Days - $dayNumber # ---| FUTURE DAYS LOOP |--- for ($daysahead = 1; $daysahead -lt $remainingdays; $daysahead++) { $futureday = $dayDate.AddDays($daysahead) # ---| WEEKEND CHECK |--- if ($futureday.DayOfWeek -in @("Saturday", "Sunday")) { Write-Debug(">>> {0} {1}: skipping weekend day" -f $futureday, $futureday.DayOfWeek, $futureds_email) continue } # ---| NEXT IN LINE FOR THE DAY-SHIFT |--- $futureds_email = $schedule.($futureday.DayOfWeek.ToString().ToLower()).'ds' $futureds_id = $(Get-MgUser -Filter "UserPrincipalName eq '$futureds_email' or proxyAddresses/any(c:c eq 'smtp:$futureds_email')").Id Write-Debug(">>> {0} {1}: {2} has it on that day" -f $futureday, $futureday.DayOfWeek, $futureds_email) # ---| THE NEXT IN LINE FOR THE DAY-SHIFT DOESN'T HAVE TIME OFF, NO-SHIFT OR ON-SITE |--- if((Invoke-HasTimeOff -UID $futureds_id -timeoff $timeoff_today -mail $futureds_email) -eq $false -and ($futureds_email -notin $os_emails) -and ($futureds_email -notin $ns_emails)) { # ---| NEXT IN LINE DOESN'T HAVE A SHIFT ALREADY |--- if((Invoke-HasShift -UID $futureds_id -shifts $shifts_today -mail $futureds_email) -eq $false) { # ---| HOME-OFFICE DAY SHIFT |--- if($futureds_email -in $ho_emails) { $newshift = Set-Shift -userId $futureds_id -groupID $group.id -shiftName "Home Office - Denní směna" -StartDate $dateStart -EndDate $dateEnd -color "purple" -teamID $team.id -mail $futureds_email $allshifts += [Object[]] $newshift $ds_shift_inplace = $true break } # ---| OFFICE DAY SHIFT |--- else { $newshift = Set-Shift -userId $futureds_id -groupID $group.id -shiftName "Office - Denní směna" -StartDate $dateStart -EndDate $dateEnd -color "purple" -teamID $team.id -mail $futureds_email $allshifts += [Object[]] $newShift $ds_shift_inplace = $true break } } # ---| NEXT IN LINE HAS A SHIFT ALREADY |--- else { # ---| LOOKING FOR THE SHIFT |--- foreach ($shift in $shifts_today) { if($shift.UserId -eq $futureds_id) { # ---| REMOVE THE SHIFT |--- Remove-Shift -shift $shift -teamID $team.Id # ---| HOME-OFFICE DAY SHIFT |--- if($futureds_email -in $ho_emails) { $newshift = Set-Shift -userId $futureds_id -groupID $group.id -shiftName "Home Office - Denní směna" -StartDate $dateStart -EndDate $dateEnd -color "purple" -teamID $team.id -mail $futureds_email $allshifts += [Object[]] $newshift $ds_shift_inplace = $true break } # ---| OFFICE DAY SHIFT |--- else { $newshift = Set-Shift -userId $futureds_id -groupID $group.id -shiftName "Office - Denní směna" -StartDate $dateStart -EndDate $dateEnd -color "purple" -teamID $team.id -mail $futureds_email $allshifts += [Object[]] $newShift $ds_shift_inplace = $true break } } } } } # ---| IF THE DAY SHIFT IS SET, BREAK OUT OF THE FUTURE DAYS LOOP |--- if($ds_shift_inplace -eq $true) { break } # ---| MOVE ON TO THE NEXT FUTURE DAY IF NO CONDITION IS MET |--- continue } } } } # ---| END OF THE DAY SHIFT CHECK SECTION |--- # ---| UPDATE COLLECTION OF SHIFTS TODAY |--- $shifts_today = [Object[]] $allshifts | Where-Object -Filter { $_.schedulingGroupId -eq $group.Id -and $_.SharedShift.StartDateTime.ToString("yyyy-MM-dd HH:mm") -ge $dateStart.ToString("yyyy-MM-dd HH:mm") -and $_.SharedShift.EndDateTime.ToString("yyyy-MM-dd HH:mm") -le $dateEnd.ToString("yyyy-MM-dd HH:mm") } # ---| ON-SITE SHIFT CHECK |--- if($email -in $os_emails) { if((Invoke-HasShiftorTimeOff -UID $userId -shifts $shifts_today -timeoff $timeoff_today -mail $email) -eq $false) { $newshift = Set-Shift -userId $userId -groupID $group.id -shiftName "On-site" -StartDate $dateStart -EndDate $dateEnd -color "gray" -teamID $team.id -mail $email $allshifts += [Object[]] $newshift continue } } # ---| NO-SHIFT CHECK |--- if($email -in $ns_emails) { Write-Debug("{0} {1}: {2} has no-shift defined for today" -f $dayDate, $dayDate.DayOfWeek, $email) continue } # ---| RLCZ MAINTENANCE CHECK |--- if($email -eq $rlcz_mtnc_email) { if ($dayDate.DayOfWeek -eq "Thursday") { $yesterday = $dayDate.AddDays(-1) $rlcz_shifts = [Object[]] $allshifts | Where-Object -Filter { $_.schedulingGroupId -eq $group.Id -and $_.SharedShift.Notes -eq "RLCZ" -and ($_.SharedShift.StartDateTime.Date -eq $yesterday.Date -or $_.SharedShift.EndDateTime.Date -eq $yesterday.Date) } if (Invoke-HasShift -UID $userId -shifts $rlcz_shifts -mail $email) { if((Invoke-HasShiftorTimeOff -UID $userId -shifts $shifts_today -timeoff $timeoff_today -mail $email) -eq $false) { if($email -in $ho_emails) { $newshift = Set-Shift -userId $userId -groupID $group.id -shiftName "Home office" -StartDate $dateStart -EndDate $dateEnd.AddHours($rlcz_shift_reduction) -color "pink" -teamID $team.id -mail $email $allshifts += [Object[]] $newshift continue } else { $newshift = Set-Shift -userId $userId -groupID $group.id -shiftName "Office" -StartDate $dateStart -EndDate $dateEnd.AddHours($rlcz_shift_reduction) -color "blue" -teamID $team.id -mail $email $allshifts += [Object[]] $newshift continue } } } } } # ---| HOME-OFFICE CHECK |--- if($email -in $ho_emails) { if((Invoke-HasShiftorTimeOff -UID $userId -shifts $shifts_today -timeoff $timeoff_today -mail $email) -eq $false) { $newshift = Set-Shift -userId $userId -groupID $group.id -shiftName "Home office" -StartDate $dateStart -EndDate $dateEnd -color "pink" -teamID $team.id -mail $email $allshifts += [Object[]] $newshift continue } } # ---| OFFICE DEFAULT |--- if((Invoke-HasShiftorTimeOff -UID $userId -shifts $shifts_today -timeoff $timeoff_today -mail $email) -eq $false) { $newshift = Set-Shift -userId $userId -groupID $group.id -shiftName "Office" -StartDate $dateStart -EndDate $dateEnd -color "blue" -teamID $team.id -mail $email $allshifts += [Object[]] $newshift continue } } } # ---| SCHEDULE SHARING |--- if($script:noplan -eq $false) { $params = @{ notifyTeam = $false startDateTime = [System.DateTime]::Parse($startSpanDate.ToString("yyyy-MM-dd'T'HH:mm:ssZ")) endDateTime = [System.DateTime]::Parse($endSpanDate.ToString("yyyy-MM-dd'T'HH:mm:ssZ")) } Invoke-MgShareTeamSchedule -TeamId $team.Id -BodyParameter $params -Headers @{ "MS-APP-ACTS-AS" = $userIdAdmin } ("SHARING: {0}" -f $scheduleGroupName) | Add-Content -Path $logPath } else { Write-Debug("Schedule not shared because of -noplan") } } catch { $_ break; }