ICAL.NET Updating Events not working using outlook. VB - vb.net

I am trying to create a calendar file (.ics) using ical.net in my vb application. My application requires appointments to be approved first. On appointment creation they are marked as pending, then when approved, it updates to Approved.
I have no issue creating the appointment or cancelling the appointment, just updating it. My appointments have both required and optional attendees. I can't figure out how to get this to work. My update ics file just adds it as a new appointment. The UID is the same in both files.
I think it has something to do with METHOD maybe, not sure. If I use PUBLISH, I can add it to my outlook calendar when I open the ICS file, but in the update ics file it just adds it as new. If I use REQUEST, it doesn't get added to my calendar. When I open it, it just has the remove option. These files will ONLY be used in outlook.
Here is my code to generate the ics file:
Public Shared Function createoutlookappt(ByVal br As benchrequest, Optional ByVal type As String = Nothing) As String
Dim recurrence As RecurrencePattern = Nothing
If Not String.IsNullOrEmpty(br.recurrinfo) Then
Dim helper As RecurrenceInfoXmlPersistenceHelper = New RecurrenceInfoXmlPersistenceHelper(New RecurrenceInfo())
Dim recInfo As RecurrenceInfo = CType(helper.FromXml(br.recurrinfo), RecurrenceInfo)
recurrence = New RecurrencePattern With {
.FirstDayOfWeek = recInfo.FirstDayOfWeek,
.Frequency = CType([Enum].Parse(GetType(FrequencyType), recInfo.Type.ToString), FrequencyType),
.Count = recInfo.OccurrenceCount,
.Until = recInfo.End,
.Interval = recInfo.Periodicity
}
End If
Dim e = New CalendarEvent
With e
.Start = New CalDateTime(CDate(br.start_time))
.[End] = New CalDateTime(CDate(br.end_time))
.DtStamp = New CalDateTime(DateTime.Now)
.Location = br.bench_name
.Priority = br.priority
.Uid = br.uid.ToString
.Sequence = CInt(br.seq)
If Not String.IsNullOrEmpty(br.allday) Then
.IsAllDay = CBool(br.allday)
End If
.LastModified = New CalDateTime(Now)
.Description = br.notes
.Summary = "(" & br.labeltxt & ") Bench Request (ID-" & br.request_id & "): " & br.program_name & " - " & br.project_name & " - " & br.activity
.RecurrenceRules = New List(Of RecurrencePattern)() From {recurrence}
.Class = "PUBLIC"
.Transparency = TransparencyType.Opaque
.Organizer = New Organizer() With {.Value = New Uri("mailto:" & br.requesting_user_email)}
End With
'Add attendees
Dim dt As New DataTable
Dim ta As New BSDataSetTableAdapters.getUserRequestTableAdapter
dt = ta.GetData(br.request_id, "T")
Dim attendee As Attendee
For Each row As DataRow In dt.Rows
If row("type") = "Required" Then
attendee = New Attendee With {
.CommonName = (row("last_name") & ", " & row("first_name")).ToString,
.Rsvp = True,
.Value = New Uri("mailto:" & row("email").ToString),
.Role = "REQ-PARTICIPANT"
}
e.Attendees.Add(attendee)
ElseIf row("type") = "Optional" Then
attendee = New Attendee With {
.CommonName = (row("last_name") & ", " & row("first_name")).ToString,
.Rsvp = False,
.Role = "OPT-PARTICIPANT",
.Value = New Uri("mailto:" & row("email").ToString)
}
e.Attendees.Add(attendee)
End If
Next
Dim calendar = New Calendar()
Select Case type
Case "Deny"
calendar.Method = "CANCEL"
Case "Approved"
calendar.Method = "PUBLISH"
Case "New Request"
calendar.Method = "PUBLISH"
Case Else
calendar.Method = "PUBLISH"
End Select
calendar.Events.Add(e)
Dim serializer = New CalendarSerializer()
Dim serializedCalendar = serializer.SerializeToString(calendar)
Dim bytesCalendar = Encoding.ASCII.GetBytes(serializedCalendar)
Dim ms As MemoryStream = New MemoryStream(bytesCalendar)
Dim filename As String = Path.GetTempPath & br.request_id & ".ics"
Dim file As New FileStream(filename, FileMode.Create, FileAccess.Write)
ms.WriteTo(file)
file.Close()
ms.Close()
Return filename
End Function
This is the initial ics file for creating the meeting:
BEGIN:VCALENDAR
METHOD:PUBLISH
PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 4.0//EN
VERSION:2.0
BEGIN:VEVENT
ATTENDEE;CN="K, David";RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:david.k#outlook.com
CLASS:PUBLIC
DESCRIPTION:testing ical
DTEND:20220406T110000
DTSTAMP:20220323T115745
DTSTART:20220406T080000
LAST-MODIFIED:20220323T115745
LOCATION:Test Bench 1
ORGANIZER:mailto:david.k#outlook.com
PRIORITY:3
RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20220525T110000;WKST=SU;COUNT=4
SEQUENCE:0
SUMMARY:(PENDING) Bench Request (ID-35): Dave Test Program - Test Project
- Bench Maintainance
TRANSP:OPAQUE
UID:ddc29fe7-12bf-4f7d-bb13-48989433c605
END:VEVENT
END:VCALENDAR
This is my update file output:
BEGIN:VCALENDAR
METHOD:PUBLISH
PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 4.0//EN
VERSION:2.0
BEGIN:VEVENT
ATTENDEE;CN="K, David";RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:david.k#outlook.com
CLASS:PUBLIC
DTEND:20220406T110000
DTSTAMP:20220323T120932
DTSTART:20220406T080000
LAST-MODIFIED:20220323T120932
LOCATION:Test Bench 1
ORGANIZER:mailto:david.k#outlook.com
PRIORITY:0
RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20220525T110000;WKST=SU;COUNT=4
SEQUENCE:0
SUMMARY:(Approved) Bench Request (ID-35): Dave Test Program - Test Project
- Bench Maintainance
TRANSP:OPAQUE
UID:ddc29fe7-12bf-4f7d-bb13-48989433c605
END:VEVENT
END:VCALENDAR
This is my cancel file output:
BEGIN:VCALENDAR
METHOD:CANCEL
PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 4.0//EN
VERSION:2.0
BEGIN:VEVENT
ATTENDEE;CN="K, David";RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:david.k#outlook.com
CLASS:PUBLIC
DTEND:20220406T110000
DTSTAMP:20220323T121655
DTSTART:20220406T080000
LAST-MODIFIED:20220323T121655
LOCATION:Test Bench 1
ORGANIZER:mailto:david.k#outlook.com
PRIORITY:0
SEQUENCE:1
SUMMARY:(Denied) Bench Request (ID-35): Dave Test Program - Test Project -
TRANSP:OPAQUE
UID:ddc29fe7-12bf-4f7d-bb13-48989433c605
END:VEVENT
END:VCALENDAR
What am I doing wrong. I have search and read many different posts and alike and nothing has worked.
My end goal. A user creates an appointment in my application where it is marked as pending. A ics file is created where they can add the appointment to their outlook calendar. An email is generated to the approvers. Once the manger goes in an approves it, an email is sent to the user letting them know it is approved (or denied) with an ics file attachment to update the appointment on their calendar (or remove it if denied). There could also be a situation where the date, time changes, so I would need to have those updates as well.
Sorry for the long post, wanted to provide as much info as possible. Thanks in advance

Related

Attempt to embed images with MailKit BodyBuilder results in attachments

I have followed the instructions for MailKit by Jeffrey Stedfast at MimeKit.net with further input from his helpful response to another Stack Overflow question to create an email with an embedded image. The result is that it saves space for the image but places the image at the end of the email as an attachment. Here is the code:
Dim clsSMTP = New MailKit.Net.Smtp.SmtpClient
clsSMTP.Connect("smtp.gmail.com", 587, False)
clsSMTP.Authenticate("xxxxxxxx#gmail.com", "xxxxxxxxxx")
strMessage = "<!DOCTYPE html><html><body style='font-family:Verdana,Arial,Tahoma,Calibri,Neue Helvetica;font-size:11pt;font-weight:normal;font-style:italic'>" _
& "<table><tr><td style='width:300px'><img src='cid:mptIntro.ContentId' style='width:300px'/></td><td> </td>" _
& "<td><h1 style='font-size:16pt;font-weight:bold;'>Time for an Advent Study</h1></td></tr></table>"
bdyBuild = New BodyBuilder
strPathIntro = "C:/Users/xxx/xxxxx/Images/xxxxxxxxxx.jpg"
mptIntro = bdyBuild.LinkedResources.Add(strPathIntro)
mptIntro.ContentId = MimeKit.Utils.MimeUtils.GenerateMessageId()
mptIntro.ContentDisposition = New MimeKit.ContentDisposition(MimeKit.ContentDisposition.Inline)
strMember = "Me"
strAttendeeEmail = "me#att.net"
Dim mmgSpecial As New MimeMessage()
mmgSpecial.From.Add(New MailboxAddress("xxx xxxxx xxxxx", "xxxxxxxxx#gmail.com"))
mmgSpecial.To.Add(New MailboxAddress(strMember, strAttendeeEmail))
mmgSpecial.Subject = "Study Ideas"
bdyBuild.HtmlBody = strMessage
mmgSpecial.Body = bdyBuild.ToMessageBody()
clsSMTP.Send(mmgSpecial)
Thank you for any help.

VB ListBox list ftp directories and remove multiple lines based on number in folder name

I do get a lot of help from this website searching for a solution but at this point I'm completely stuck. I'm not a programmer, still was able to get that far.
The idea of my little project is to download newest version of a program (folder) from FTP server. Unzip files and update current program folder with new files. But before that backup the exiting program folder to a different FTP address.
What I'm struggling with is a way they post program versions (folders) on FTP:
folder structure on FTP
I can't really predict what would be the next folder to download because e.g. for program version:
7.6.16
it might be:
WERSJA_NOWY_TEMPLATE_7.6.17
or
WERSJA_NOWY_TEMPLATE_7.7.01
what ever is being released.
There is an ini file on C drive that holds the current program version. I was able to retrieve that information by reading the line, removing the unnecessary characters and splitting the string:
Dim wersja1 As String = File.ReadAllLines("C:\Windows\file.ini").FirstOrDefault(Function(x) x.Contains("WERSJA="))
Dim characterToRemove1 As String = "WERSJA="
Dim wersja2 As String = Replace(wersja1, characterToRemove1, "")
Dim characterToRemove2 As String = "T"
Dim wersja3 As String = Replace(wersja2, characterToRemove2, "")
Dim wersja4 As String() = wersja3.Split(New Char() {"."c})
Dim w1 As String = wersja4(0)
Dim w2 As String = wersja4(1)
Dim w3 As String = wersja4(2)
e.g. from a line:
WERSJA=7.6.5T
I get:
w1 = 7
w2 = 6
w3 = 5
Then I change the last one (w3) to a two digits number (because of the way folders are named on FTP):
If w3 < 10 Then
w3 = "0" & w3
Else
w3 = w3
End If
So if:
w3 = 5 >> I get 05
w3 = 16 >> I get 16
So far, so good.
In the next step I would like to see a filtered list of program versions (folders) in a ListBox or 2 ListBoxes. I would like to filter the list to see only folder with a bigger number than the current program version, so I can choose the correct one to download.
Why this way? Because if current version is e.g.
7.6.16
and there are 3 new ones e.g.:
7.6.17
7.6.18
7.7.01
I need to download all of them one by one, update program files and start the program to update sql database in order they were released. I can't skip any version on update.
Dim FTPurl As String = "ftp://xx.xx.xxx.xxx/wersje/"
Dim FTPwersja As String = "WERSJA_NOWY_TEMPLATE_*"
Dim FTPlog As String = "xxx"
Dim FTPpass As String = "yyy"
Dim clsRequest As FtpWebRequest = System.Net.WebRequest.Create(FTPurl & FTPwersja)
clsRequest.Credentials = New System.Net.NetworkCredential(FTPlog, FTPpass)
clsRequest.Method = System.Net.WebRequestMethods.Ftp.ListDirectory
Dim listResponse As FtpWebResponse = clsRequest.GetResponse
Dim reader As StreamReader = New StreamReader(listResponse.GetResponseStream())
'folder list
While reader.Peek >= 0
ListBox1.Items.Add(reader.ReadLine)
End While
'remove empty lines
For i As Integer = ListBox1.Items.Count - 1 To 0 Step -1
If ListBox1.GetItemText(ListBox1.Items(i)) = String.Empty Then
ListBox1.Items.RemoveAt(i)
End If
Next i
The above code gives me this result.
I found this code that enables filtering of ListBox but I have no clue on how to convert it to an loop so I would see only folders with bigger numbers than the current version of program:
'filter result in list box
Dim items = From it In ListBox1.Items.Cast(Of Object)()
Where it.ToString().IndexOf("7.5", StringComparison.CurrentCultureIgnoreCase) >= 0
Dim matchingItemList As List(Of Object) = items.ToList()
ListBox1.BeginUpdate()
'ListBox1.Items.Clear() 'add to list
For Each item In matchingItemList
ListBox1.Items.Remove(item) 'remove from list
'ListBox1.Items.Add(item) 'add to list
Next
ListBox1.EndUpdate()
I also have this code to catch choice and prevent crash when clicked on empty line :
Private Sub ListBox1_SelectedIndexChanged(sender As Object, e As EventArgs) _
Handles ListBox1.SelectedIndexChanged
Try
Dim LB1choice = ListBox1.SelectedItem.ToString()
Label5.Text = LB1choice
Catch ex As Exception
ListBox1.SelectedIndex = 0
End Try
End Sub
The ideal would be 2 ComboBoxes where 1st would display e.g.
WERSJA_NOWY_TEMPLATE_7.6
WERSJA_NOWY_TEMPLATE_7.7
and based on the choice 2nd ComboBox would list newer subfolders in it e.g.
WERSJA_NOWY_TEMPLATE_7.6.16
WERSJA_NOWY_TEMPLATE_7.6.17
for the 1st one, and:
WERSJA_NOWY_TEMPLATE_7.7.01
for the 2nd one.
I much appreciate if anyone could help me this because it's beyond my skills.

VB - Sorting Alphabetically From a CSV File

I don't know a lot about the subject of sorting but here goes: I am trying to sort a music library (comma seperated in a csv file. Some examples):
1,Sweet Home Alabame,Lynyrd Skynyrd,4:40,Classic Rock
2,Misirlou,Dick Dale,2:16,Surf Rock
I need to sort them alphabetically (by title of track) but I don't know two things: 1. Why my current technique isn't working:
Dim array() As String = {}
sr = New StreamReader("library.csv")
counter = 1
Do Until sr.EndOfStream
array(counter) = sr.ReadLine()
counter += 1
Loop
System.Array.Sort(Of String)(array)
Dim value As String
For Each value In array
Console.WriteLine(value)
Next
Console.ReadLine()
I don't know if this is the best way of sorting. I then need to display them as well. I can do this without sorting, but can't figure out how to do it with sorting.
Help please (from people who, unlike me, know what they're doing).
Right now you're putting all fields in one long string of text (each row).
In order to sort by a particular field, you'll need to build a matrix of rows and columns. For example, a DataTable.
Here's a class that should do the trick for you:
https://www.codeproject.com/Articles/11698/A-Portable-and-Efficient-Generic-Parser-for-Flat-F
Here's the sample usage code from the article, translated to VB:
Public Class CsvImporter
Public Sub Import()
Dim dsResult As DataSet
' Using an XML Config file.
Using parser As New GenericParserAdapter("MyData.txt")
parser.Load("MyData.xml")
dsResult = parser.GetDataSet()
End Using
' Or... programmatically setting up the parser for TSV.
Dim strID As String, strName As String, strStatus As String
Using parser As New GenericParser()
parser.SetDataSource("MyData.txt")
parser.ColumnDelimiter = vbTab.ToCharArray()
parser.FirstRowHasHeader = True
parser.SkipStartingDataRows = 10
parser.MaxBufferSize = 4096
parser.MaxRows = 500
parser.TextQualifier = """"c
While parser.Read()
strID = parser("ID")
strName = parser("Name")
' Your code here ...
strStatus = parser("Status")
End While
End Using
' Or... programmatically setting up the parser for Fixed-width.
Using parser As New GenericParser()
parser.SetDataSource("MyData.txt")
parser.ColumnWidths = New Integer(3) {10, 10, 10, 10}
parser.SkipStartingDataRows = 10
parser.MaxRows = 500
While parser.Read()
strID = parser("ID")
strName = parser("Name")
' Your code here ...
strStatus = parser("Status")
End While
End Using
End Sub
End Class
There's also this from here, demonstrating DataTable usage:
Dim csv = "Name, Age" & vbCr & vbLf & "Ronnie, 30" & vbCr & vbLf & "Mark, 40" & vbCr & vbLf & "Ace, 50"
Dim reader As TextReader = New StringReader(csv)
Dim table = New DataTable()
Using it = reader.ReadCsvWithHeader().GetEnumerator()
If Not it.MoveNext() Then
Return
End If
For Each k As var In it.Current.Keys
table.Columns.Add(k)
Next
Do
Dim row = table.NewRow()
For Each k As var In it.Current.Keys
row(k) = it.Current(k)
Next
table.Rows.Add(row)
Loop While it.MoveNext()
End Using
And this Q&A illustrates how to sort the DataTable by a given column.

FileInfo returning wrong value?

Okay, so I'm working in VB.NET, manually writing error logs to log files (yes, I know, I didn't make the call). Now, if the files are over an arbitrary size, when the function goes to write out the new error data, it should start a new file with a new file name.
Here's the function:
Dim listener As New Logging.FileLogTraceListener
listener.CustomLocation = System.Configuration.ConfigurationManager.AppSettings("LogDir")
Dim loc As String = DateTime.UtcNow.Year.ToString + DateTime.UtcNow.Month.ToString + DateTime.UtcNow.Day.ToString + DateTime.UtcNow.Hour.ToString + DateTime.UtcNow.Minute.ToString
listener.BaseFileName = loc
Dim logFolder As String
Dim source As String
logFolder = ConfigurationManager.AppSettings("LogDir")
If ex.Data.Item("Source") Is Nothing Then
source = ex.Source
Else
source = ex.Data.Item("Source").ToString
End If
Dim errorFileInfo As New FileInfo(listener.FullLogFileName)
Dim errorLengthInBytes As Long = errorFileInfo.Length
If (errorLengthInBytes > CType(System.Configuration.ConfigurationManager.AppSettings("maxFileSizeInBytes"), Long)) Then
listener.BaseFileName = listener.BaseFileName + "1"
End If
Dim msg As New System.Text.StringBuilder
If String.IsNullOrEmpty(logFolder) Then logFolder = ConfigurationManager.AppSettings("LogDir")
msg.Append(vbCrLf & "Exception" & vbCrLf)
msg.Append(vbTab & String.Concat("App: AppMonitor | Time: ", Date.Now.ToString) & vbCrLf)
msg.Append(vbTab & String.Concat("Source: ", source, " | Message: ", ex.Message) & vbCrLf)
msg.Append(vbTab & "Stack: " & ex.StackTrace & vbCrLf)
listener.Write(msg.ToString())
listener.Flush()
listener.Close()
I have this executing in a loop for testing purposes, so I can see what happens when it gets (say) 10000 errors in all at once. Again, I know there are better ways to handle this systemically, but this was the code I was told to implement.
How can I reliably get the size of the log file before writing to it, as I try to do above?
Well, as with many things, the answer to this turned out to be "did you read your own code closely" with a side order of "eat something, you need to fix your blood sugar."
On review, I saw that I was always checking BaseFileName and, if it was over the arbitrary limit, appending a character and writing to that file. What I didn't do was check to see if that file or, indeed, other more recent files existed. I've solved the issue be grabbing a directory list of all the files matching the "BaseFileName*" argument in Directory.GetFiles and selecting the most recently accessed one. That ensures that the logger will always select the more current file to write to or -if necessary- use as the base-name for another appended character.
Here's that code:
Dim directoryFiles() As String = Directory.GetFiles(listener.Location.ToString(), listener.BaseFileName + "*")
Dim targetFile As String = directoryFiles(0)
For j As Integer = 1 To directoryFiles.Count - 1 Step 1
Dim targetFileInfo As New FileInfo(targetFile)
Dim compareInfo As New FileInfo(directoryFiles(j))
If (targetFileInfo.LastAccessTimeUtc < compareInfo.LastAccessTimeUtc) Then
targetFile = directoryFiles(j)
End If
Next
Dim errorFileInfo As New FileInfo(listener.Location.ToString() + targetFile)
Dim errorLengthInBytes As Long = errorFileInfo.Length

Index was outside the bounds of the array [VB.NET]

Hi i am new to VB and in the process of learning. This error occur sometimes and doesn't occur sometimes which i find it weird.
I receive the error Index was outside the bounds of the array, that points to Button30.Text = Split(newestversion, vbCrLf)(**1**)
My motive is to read line by line from a online hosted text file.
For example,
label1.text = line 1 of the text file
label2.text = line 2 of the text file
This is very much what i want.
Here is my current code (EDITED):
Dim request As System.Net.HttpWebRequest = System.Net.HttpWebRequest.Create("direct link to my online txt file")
Dim response As System.Net.HttpWebResponse = request.GetResponse
Dim sr As System.IO.StreamReader = New System.IO.StreamReader(response.GetResponseStream)
Dim stringReader As String
stringReader = sr.ReadLine()
Button10.Text = stringReader
Dim newestversion As String = sr.ReadToEnd
Dim currentversion As String = Application.ProductVersion
Dim part() As String = Split(newestversion, vbCrLf)
If part.Length < 10 Then
' not enough items in the array. You could also throw and exception or do some other stuff here
Label10.Text = "beta"
Exit Sub
End If
'updates new episode numbers on buttons
Button20.Text = part(0)
Button30.Text = part(1)
Button40.Text = part(2)
Button50.Text = part(3)
Button60.Text = part(4)
Button70.Text = part(5)
Button80.Text = part(6)
Button90.Text = part(7)
Button100.Text = part(8)
Button110.Text = part(9)
End If
Thank You!!
You split your String for line breaks. This gives you an array, having one entry for each line in the String. However, you do not check if this array holds the amount of items you expect. You could do:
Dim newestversion As String = sr.ReadToEnd
Dim currentversion As String = Application.ProductVersion
Dim part() As String = Split(newestversion, vbCrLf)
If part.Length < 10 Then
' not enough items in the array. You could also throw and exception or do some other stuff here
MsgBox(String.Format("Array only has {0} items", part.Length))
Exit Sub
End If
'updates new episode numbers on buttons
Button20.Text = part(0)
Button30.Text = part(1)
Button40.Text = part(2)
...
Edit for the updated question
If you do have a problem like this, just approach it systematically and get as much information as you can. First you have to check if you really get the data you want from the remote source. To do that, add some logging (e.g. a MsgBox(newestversion) or a real log file). Check if the data you get is what you expect. If not, there's already a problem with your request/response code, which is a completely different problem than what I provided a solution for. If newestversion is OK, check if the splitting works by printing out the part() array. Maybe the server uses a different operating system or just uses vbCr as newline and not vbCrlf. If the splitting also works, you are done.