diff --git a/playbook/shopfloor-setup/common/scripts/Backup-PartMarker.ps1 b/playbook/shopfloor-setup/common/scripts/Backup-PartMarker.ps1 index 824343f..9f4c217 100644 --- a/playbook/shopfloor-setup/common/scripts/Backup-PartMarker.ps1 +++ b/playbook/shopfloor-setup/common/scripts/Backup-PartMarker.ps1 @@ -46,6 +46,12 @@ # Always exits 0 so the GE-Enforce "last run result" stays clean. Failures are # logged, never thrown. +# CmdletBinding so an unknown argument is an ERROR, not a shrug. Without it a +# script's param block silently drops unrecognised named arguments into $args - +# so a typo in the manifest entry's Args ("-WindowStartMinutes 45") would be +# ignored and the capture would quietly run at the wrong time, with nothing in +# the log to say why. Cost me an hour of debugging a gate that was fine. +[CmdletBinding()] param( [string]$BaseUrl = '', # Root of the opaque-backup tree. The snapshot lands in @@ -80,6 +86,12 @@ param( # GE-Enforce fires every 5 minutes, so this runs on the first cycle inside # the hour and the 24h throttle stops it running again that day. [int[]]$WindowHours = @(14), + # Minute the window OPENS within those hours. 0 means the whole hour. + # Set from the manifest entry's Args when the first run of the day should + # land at a particular time - the shift change is a moment, not an hour, + # and a capture is easiest to watch when somebody expects it. + [ValidateRange(0, 59)] + [int]$WindowStartMinute = 0, # Used ONLY once the backup is overdue - the other two shift changes. They # are unattended, so they are a fallback for a bay that is busy at 14:00 # every day, not part of the normal schedule. @@ -178,7 +190,11 @@ if ($MyInvocation.InvocationName -eq '.') { return } # out-of-window cycle costs a clock read and nothing else. if (-not $Force) { $now = Get-Date - $inwindow = ($WindowHours -contains $now.Hour) + # Both the hour AND the minute, so a window can be "14:45 onwards" rather + # than the whole of the 14:00 hour. GE-Enforce fires every 5 minutes, so + # the capture starts on the first cycle at or after that time and the 24h + # throttle stops it running again that day. + $inwindow = ($WindowHours -contains $now.Hour) -and ($now.Minute -ge $WindowStartMinute) # The marker file is stamped on every post attempt, so its age answers # "when did a capture last get through" - which is what the escalation @@ -213,7 +229,8 @@ if (-not $Force) { # marking parts and the port is not ours to take. $allowed = $inwindow if (-not $allowed -and $overdue) { - $allowed = ($OverdueWindowHours -contains $now.Hour) + $allowed = ($OverdueWindowHours -contains $now.Hour) -and + ($now.Minute -ge $WindowStartMinute) } if (-not $allowed) { @@ -232,7 +249,7 @@ if (-not $Force) { # was built to stop. Write-ShopdbQuietState -Kind $KIND -State 'outside-window' -Message ( 'Outside the capture window ({0}). The marker port is shared with eDNC, so the daily capture waits for a shift change. Use -Force to override.' -f - (($WindowHours | ForEach-Object { '{0:00}:00' -f $_ }) -join ', ')) + (($WindowHours | ForEach-Object { '{0:00}:{1:00}' -f $_, $WindowStartMinute }) -join ', ')) } exit 0 }