I’m building a Windows service using VB.NET that needs to check a user’s Google Calendar every few minutes to find upcoming appointments within a 10-minute window.
Current Setup Issues
Right now I have two separate applications. My main service can’t show browser windows for OAuth authentication, so I created a console application that handles the authentication flow and saves credentials to a shared folder. The service then reads these saved credentials.
Main Problems
- Token expiration - My biggest headache is that tokens expire after about an hour and the service stops working
- Initial authentication - Having to run a separate console app feels clunky
Windows Service Code (VB.NET)
Dim ApiScopes As String() = {CalendarService.Scope.CalendarReadonly}
Dim AppName As String = "Calendar Monitor Service"
Private Sub CheckCalendarEvents(sender As Object, args As ElapsedEventArgs)
Dim userCred As UserCredential
Try
Using fileStream = New FileStream("C:\Config\client_secrets.json", FileMode.Open, FileAccess.Read)
Dim tokenPath As String = "C:\Tokens"
tokenPath = Path.Combine(tokenPath, ".auth/calendar-service.json")
userCred = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(fileStream).Secrets,
ApiScopes,
"serviceuser",
CancellationToken.None,
New FileDataStore(tokenPath, True)).Result
If userCred Is Nothing Then
userCred.RefreshTokenAsync(CancellationToken.None)
End If
End Using
Dim calendarService = New CalendarService(New BaseClientService.Initializer() With {
.HttpClientInitializer = userCred,
.ApplicationName = AppName
})
Dim eventsQuery As EventsResource.ListRequest = calendarService.Events.List("primary")
eventsQuery.TimeMin = DateTime.Now
eventsQuery.TimeMax = DateTime.Now.AddMinutes(10)
eventsQuery.ShowDeleted = False
eventsQuery.SingleEvents = True
eventsQuery.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime
Dim calendarEvents As Events = eventsQuery.Execute()
If calendarEvents.Items IsNot Nothing AndAlso calendarEvents.Items.Count > 0 Then
ServiceEventLog.WriteEntry("Found upcoming appointments")
Else
ServiceEventLog.WriteEntry("No appointments in next 10 minutes")
End If
Catch ex As Exception
ServiceEventLog.WriteEntry("Calendar check failed: " & ex.Message)
End Try
End Sub
Console App for Authentication
Module AuthModule
Dim Permissions As String() = {CalendarService.Scope.CalendarReadonly}
Dim ProgramName As String = "Calendar Auth Helper"
Sub Main()
Dim authCredential As UserCredential
Using configStream = New FileStream("client_secrets.json", FileMode.Open, FileAccess.Read)
Dim credentialStorage As String = "C:\Tokens"
credentialStorage = Path.Combine(credentialStorage, ".auth/calendar-service.json")
authCredential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(configStream).Secrets,
Permissions,
"serviceuser",
CancellationToken.None,
New FileDataStore(credentialStorage, True)).Result
Console.WriteLine("Authentication saved to: " & credentialStorage)
End Using
Dim apiService = New CalendarService(New BaseClientService.Initializer() With {
.HttpClientInitializer = authCredential,
.ApplicationName = ProgramName
})
Dim testQuery As EventsResource.ListRequest = apiService.Events.List("primary")
testQuery.TimeMin = DateTime.Now
testQuery.ShowDeleted = False
testQuery.SingleEvents = True
testQuery.MaxResults = 5
testQuery.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime
Dim testResults As Events = testQuery.Execute()
Console.WriteLine("Recent events:")
If testResults.Items IsNot Nothing AndAlso testResults.Items.Count > 0 Then
For Each appointment As Object In testResults.Items
Dim eventTime As String = appointment.Start.DateTime.ToString()
If String.IsNullOrEmpty(eventTime) Then
eventTime = appointment.Start.Date
End If
Console.WriteLine("{0} - {1}", appointment.Summary, eventTime)
Next
Console.WriteLine("Authentication successful. Press any key to exit.")
Else
Console.WriteLine("No events found but authentication worked.")
End If
Console.ReadKey()
End Sub
End Module
Is there a better way to handle token refresh automatically in the service? Should I be using service accounts instead?