MigrateSQLVMPatchingSchedule.psm1
<#
.SYNOPSIS Script for migrating from Autopatching schedule on SQL VM to Azure Update Manager(AUM) schedule .DESCRIPTION This script is for migrating from Autopatching schedule on SQL VM to Azure Update Manager(AUM) schedule Before running this script, ensure you have: - SQL VM is running - Autopatching is enabled .PARAMETER ResourceGroupName Name of the ResourceGroup whose VMs need to be migrated .PARAMETER VmName Name of the VM to be migrated .EXAMPLE import-module -Name ".\MigrateSQLVMPatchingSchedule.psm1" Migrate-SQLVMPatchingSchedule -ResourceGroupName myrg -VmName myvm-1 #> function Migrate-SQLVMPatchingSchedule { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $ResourceGroupName, [Parameter(Mandatory = $true)] [string] $VmName) $context = Get-AzContext if ($context -eq $null) { Connect-AzAccount } #set values $configName = "MigratedSchedule-"+$VmName $WindowsParameterClassificationToInclude = "Critical", "Security"; $RebootOption = "IfRequired" $scope = "InGuestPatch" # Get the VM details $vm = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VmName -ErrorAction Stop # Get the subscription ID $SubscriptionId = $vm.Id.Split('/')[2] $location = $vm.Location try { # get current autopatching settings $currentPatchSettings = (Get-AzVMSqlServerExtension -VMName $VmName -ResourceGroupName $ResourceGroupName).AutoPatchingSettings if ($currentPatchSettings -eq $null -or $currentPatchSettings.Enable -eq $false) { Write-Warning "Auto patching is not enabled on VM." return } Write-Host "current autopatching settings:: " Write-Host " Maintenance schedule: $($currentPatchSettings.DayOfWeek)" Write-Host " Maintenance start hour (local time): $($currentPatchSettings.MaintenanceWindowStartingHour)" Write-Host " Maintenance window duration (minutes): $($currentPatchSettings.MaintenanceWindowDuration) " } catch { Write-Error "An error occurred while trying to retrieve the autopatching settings." Write-Error "Error message: $_" return } try { $convertedSchedule = Convert-Schedule -ResourceGroupName $ResourceGroupName -VmName $VmName ` -DayOfWeek $currentPatchSettings.DayOfWeek ` -MaintenanceWindowStartingHour $currentPatchSettings.MaintenanceWindowStartingHour ` -MaintenanceWindowDuration $currentPatchSettings.MaintenanceWindowDuration } catch { Write-Error "An error occurred while converting autopatching settings to AUM schedule." Write-Error "Error message: $_" return } # enable MU try { $temp = Set-AzVMOperatingSystem -VM $vm -Windows -PatchMode "AutomaticByPlatform" $AutomaticByPlatformSettings = $vm.OSProfile.WindowsConfiguration.PatchSettings.AutomaticByPlatformSettings if ($null -eq $AutomaticByPlatformSettings) { $vm.OSProfile.WindowsConfiguration.PatchSettings.AutomaticByPlatformSettings = New-Object -TypeName Microsoft.Azure.Management.Compute.Models.WindowsVMGuestPatchAutomaticByPlatformSettings -Property @{BypassPlatformSafetyChecksOnUserSchedule = $true} } else { $AutomaticByPlatformSettings.BypassPlatformSafetyChecksOnUserSchedule = $true } $temp = Update-AzVM -VM $vm -ResourceGroupName $ResourceGroupName } catch { Write-Error "An error occurred while enabling MU on the VM." Write-Error "Error message: $_" return } try { # create new maintenance config $maintConfig = New-AzMaintenanceConfiguration ` -ResourceGroup $ResourceGroupName ` -Name $configName ` -MaintenanceScope $scope ` -Location $location ` -StartDateTime $convertedSchedule.scheduleStartOn ` -TimeZone $convertedSchedule.timezoneId ` -Duration $convertedSchedule.duration ` -RecurEvery $convertedSchedule.recurEvery ` -WindowParameterClassificationToInclude $WindowsParameterClassificationToInclude ` -InstallPatchRebootSetting $RebootOption ` -ExtensionProperty @{"InGuestPatchMode"="User"} } catch { Write-Error "An error occurred while creating new maintenance configuration." Write-Error "Error message: $_" return } # assign VM to maintenance config try { $temp = New-AzConfigurationAssignment ` -ResourceGroupName $ResourceGroupName ` -Location $location ` -ResourceName $VmName ` -ResourceType "VirtualMachines" ` -ProviderName "Microsoft.Compute" ` -ConfigurationAssignmentName $configName ` -MaintenanceConfigurationId $maintConfig.Id } catch { Write-Error "An error occurred while assigning the maintenance configuration to VM." Write-Error "Error message: $_" return } Write-Host "Attached new maintenance schedule $configName to VM : $VmName" Write-Host "New maintenance schedule:: " Write-Host " Recurrence: $($maintConfig.RecurEvery)" Write-Host " Schedule start time: $($maintConfig.StartDateTime)" Write-Host " Duration: $($maintConfig.Duration)" Write-Host " Schedule expiration time: $($maintConfig.ExpirationDateTime)" try { # disable automated patching, this takes upto 1-2 mins Write-Host "Disabling Autopatching setting..." $temp = Update-AzSqlVM -ResourceGroupName $ResourceGroupName -Name $VmName -AutoPatchingSettingEnable:$false } catch { Write-Error "An error occurred while disabling Autopatching. Please update manually." #Write-Error "Error message: $_" } } <# .SYNOPSIS Function for getting current timezone id on VM. If not found, prompts user to enter timezone id .DESCRIPTION This function is for getting current timezone id on VM. If not found, prompts user to enter timezone id .PARAMETER ResourceGroupName Name of the ResourceGroup .PARAMETER VmName Name of the VM .EXAMPLE Get-TimezoneId -ResourceGroupName myrg -VmName myvm-1 #> function Get-TimezoneId { Param ( [Parameter(Mandatory = $true)] [string] $ResourceGroupName, [Parameter(Mandatory = $true)] [string] $VmName) try { Write-Host "Reading timezone on VM" $rgName = $ResourceGroupName $vmName = $VmName $output = Invoke-AzVmRunCommand ` -ResourceGroupName $rgName ` -VMName $vmName ` -CommandId "RunPowerShellScript" ` -ScriptString "Get-Timezone" $timezoneinfo = $output.value[0].message $timezoneId = ($timezoneinfo | Select-String -Pattern '^Id\s*:\s*(.*)').Matches.Groups[1].Value return $timezoneId } catch { # Write-Error "An error occurred while getting Timezone on Vm: $_" # return "" } if ($timezoneId -eq $null -or $timezoneId -eq "") { Write-Host "Could not read Timezone from VM" $valid = $false while (-not $valid) { # Prompt user to enter timezone ID $timezoneId = Read-Host "Please enter a timezone ID. Refer to https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones?view=windows-11 for valid timezones" # Validate the entered timezone ID $valid = Validate-TimezoneId -TimezoneId $timezoneId } Write-Host "you entered timezone: $($timezoneId)" return $timezoneId } } <# .SYNOPSIS Function for validating timezone id entered by user .DESCRIPTION This function is for validating timezone id entered by user .PARAMETER TimezoneId time zone id value .EXAMPLE Validate-TimezoneId -TimezoneId "UTC" #> function Validate-TimezoneId { Param ( [string]$TimezoneId ) # Define valid timezone IDs $validTimezoneIds = [System.TimeZoneInfo]::GetSystemTimeZones().Id if ($validTimezoneIds -contains $TimezoneId) { return $true } else { Write-Host "Invalid timezone ID: $TimezoneId" return $false } } <# .SYNOPSIS Function for converting current autopatching schedule to AUM schedule .DESCRIPTION This function is converting current autopatching schedule to AUM schedule .PARAMETER ResourceGroupName Name of the ResourceGroup .PARAMETER VmName Name of the VM .PARAMETER DayOfWeek Current DayOfWeek setting .PARAMETER MaintenanceWindowStartingHour Current MaintenanceWindowStartingHour value .PARAMETER MaintenanceWindowDuration Current MaintenanceWindowDuration value .EXAMPLE Convert-Schedule -ResourceGroupName myrg -VmName myvm-1 -DayOfWeek "Sunday" -MaintenanceWindowStartingHour 22 -MaintenanceWindowDuration 90 #> function Convert-Schedule { Param ( [Parameter(Mandatory = $true)] [string] $ResourceGroupName, [Parameter(Mandatory = $true)] [string] $VmName, [Parameter(Mandatory = $true)] [string] $DayOfWeek, [Parameter(Mandatory = $true)] [int] $MaintenanceWindowStartingHour, [Parameter(Mandatory = $true)] [int] $MaintenanceWindowDuration) $timezoneId = Get-TimezoneId -ResourceGroupName $ResourceGroupName -VMName $VmName $currentDateTime = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId([DateTime]::Now,$timezoneId) $currentDateWithMaintenanceStartTime = [DateTime]($currentDateTime.ToString('yyyy-MM-dd') + " " + "{0:hh\:mm}" -f (New-TimeSpan -Hours $MaintenanceWindowStartingHour)) # if schedule is daily, then the start time should be today # unless the time has already passed, then it would be the next day # else if the schedule is weekly, and the current day matches today, then the start day is today, # unless the time has passed, then it would be the next day that matches the day of week if($DayOfWeek -eq "Everyday") { $recurEvery = "Day" if ($currentDateTime -lt $currentDateWithMaintenanceStartTime) { $scheduleStartOn = $currentDateTime.ToString('yyyy-MM-dd') + " " + "{0:hh\:mm}" -f (New-TimeSpan -Hours $MaintenanceWindowStartingHour) } else { $scheduleStartOn = $currentDateTime.AddDays(1).ToString('yyyy-MM-dd') + " " + "{0:hh\:mm}" -f (New-TimeSpan -Hours $MaintenanceWindowStartingHour) } } else { $recurEvery = "Week " + $DayOfWeek if (($currentDateTime -lt $currentDateWithMaintenanceStartTime) -and ($currentDateTime.DayOfWeek -eq $DayOfWeek)) { $scheduleStartOn = $currentDateTime.ToString('yyyy-MM-dd') + " " + "{0:hh\:mm}" -f (New-TimeSpan -Hours $MaintenanceWindowStartingHour) } else { for($i=1; $i -le 7; $i++) { if($currentDateTime.AddDays($i).DayOfWeek -eq $DayOfWeek) { $scheduleStartOn = $currentDateTime.AddDays($i).ToString('yyyy-MM-dd') + " " + "{0:hh\:mm}" -f (New-TimeSpan -Hours $MaintenanceWindowStartingHour) break } } } } if($MaintenanceWindowDuration -le 90) { $duration = "01:30" } else { $duration = "{0:hh\:mm}" -f (New-TimeSpan -Minutes $MaintenanceWindowDuration) } return @{ recurEvery = $recurEvery scheduleStartOn = $scheduleStartOn duration = $duration timezoneId = $timezoneId } } Export-ModuleMember -Function Migrate-SQLVMPatchingSchedule |