OpenXML in Microsoft Excel for Comments - c#

I am running the latest Office 365 Excel version, 1901. I have updated to the latest OpenXml SDK but am unable to figure out how to programmatically read information about threaded comments, as all I'm seeing is a full summary comment. Even using the latest OpenXml Nuget package.
If I convert the Excel document to a .zip file, I can see "threadedComments.xml" files which has what I need, but do not know how to go about it programmatically in C# .NET.

I know, you didn't watch for VBA, but there the new CommentThreaded object now works at least (Excel version 1906, tested June 2019).
I actually tested it in Visual Studio C#, but it still seems to be not supported.
As of May 15th 2019 the new object CommentThreaded is described by Microsoft.
In my Excel version 1906, it's fully supported in VBA.
Here's some VBA-code to explain the handling a little:
Private Sub ExcelsNewCommentThreaded()
Dim AllCommentsThreaded As Excel.CommentsThreaded
Dim OneCommentThreaded As Excel.CommentThreaded
Dim AllReplies As Excel.CommentsThreaded
Dim OneReply As Excel.CommentThreaded
Dim r As Range
Set AllCommentsThreaded = ActiveSheet.CommentsThreaded
' loop over all threaded comments of a worksheet and get their info
For Each OneCommentThreaded In AllCommentsThreaded
With OneCommentThreaded
Debug.Print .Author.Name, .Date, .Text
For Each OneReply In .Replies
With OneReply
Debug.Print .Author.Name, .Date, OneReply.Text
End With
Next OneReply
End With
Next OneCommentThreaded
Set r = Selection.Cells(1)
' check if the selected cell already contains a threaded comment
If r.CommentThreaded Is Nothing Then
r.AddCommentThreaded ("my new comment")
End If
With r.CommentThreaded
' get text of comment
Debug.Print .Text
' add some replies
.AddReply ("my reply 1")
.AddReply ("my reply 2")
' change text of comment
Debug.Print .Text(Text:="text of comment changed")
Debug.Print .Text
' change text of a reply
.Replies(1).Text Text:="text of reply 1 changed"
Debug.Print .Replies(1).Text
' delete second reply
.Replies(2).Delete
' delete whole comment including its replies
.Delete
End With
End Sub

You can access to content programmatically, if you know exact location in .zip archieve:
static class Program
{
static void Main(string[] args)
{
using (var archive = ZipFile.OpenRead(args[0]))
{
var entry = archive.Entries.Where(_ => _.FullName.Equals("xl/comments1.xml", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
if (entry != null)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var data = new List<string>(Decompress(entry.Open()));
var graph = new Graph(data);
stopwatch.Watch();
Console.ReadLine();
}
}
}
public static IEnumerable<string> Decompress(Stream stream)
{
using (var reader = new StreamReader(stream, Encoding.ASCII))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
}

Related

Reading the Mail Body content on Outlook Addin VSTO

I am developing an Outlook add-in using VSTO that checking spelling of the mail content while composing.
In Reply mails, How can I check only the reply content by excluding the old conversation ?
This is what I am doing now.But I need to know whether there is any proper object or method to get current reply content.
Outlook.MailItem mailItem = inspector.CurrentItem as Outlook.MailItem;
string temp = mailItem.Body;
int target= temp.IndexOf("\r\nFrom:");
string contentToCheck= temp.Substring(0, target);
There is no built in property that signifies the end of the new message and the start of a reply in body text.
What can be done is store common text that marks the end of the new message and check for that when reading the body. Things like ' FROM:', 'Regards,' 'Thanks,'...
Something similar to:
while ((line = bodytext.ReadLine()) != null)
{
foreach(string ending in MYLISTOFCOMMONENDINGS)
{
if (line.StartsWith(ending))
return sb.ToString(); //we are done
//here sb is a string builder consuming new lines
}
//read the line and check spelling
}
Look for the bookmark named "_MailOriginal". The script below inserts text just before the original message begins:
set objDoc = Application.ActiveInspector.WordEditor
If objDoc.Bookmarks.Exists("_MailOriginal") Then
' is there the original email? (_MailOriginal)
set objBkm = objDoc.Bookmarks("_MailOriginal")
Set objSel = objDoc.Application.Selection
objSel.Start = objBkm.Start-2 'give room for the line break before. It includes the line
objSel.End = objSel.Start
objSel.Text = "test"
End If

How to Apply Parallelism in VB.NET to execute 130 batch files Saved in one folder?

I have 130 batch files stored in one central location, I want to create VB.NET program to execute those batch files in parallel NOT in sequence. and then return the output of these 130 batch files to Textbox control.
How can I cater the above requirements? or what's the recommended way of doing it, keeping in mind the program will run on a high Specs Server so performance is not an issue here.
What I have done is I placed this for loop inside Button_Click Event to get my .bat names & then to get them executed in parallel:
Dim Directory As New IO.DirectoryInfo("C:\Batch\")
Dim fileName As IO.FileInfo() = Directory.GetFiles("*.bat")
For Each file As IO.FileInfo In fileName
RichTextBox1.AppendText(file.ToString() & vbNewLine)
Next
But now here after i got the names of .dat, how to get them executed in parallel?
This is as close as I could get, combining the comments and the updated description
Dim Directory As New IO.DirectoryInfo("C:\Batch\")
Dim fileName As IO.FileInfo() = Directory.GetFiles("*.bat")
Dim list As New List(Of String)
For Each file As IO.FileInfo In fileName
list.Add(file.ToString())
Next
Dim processTasks As New List(Of Task(Of String))()
For Each bat As String In list
Dim processTask As Task(Of String) = Task.Factory.StartNew((Function()
Dim filePath As String = IO.Path.Combine(myDirectory.FullName, bat)
Dim info As New System.Diagnostics.ProcessStartInfo()
info.FileName = filePath
info.CreateNoWindow = True
info.WindowStyle = ProcessWindowStyle.Hidden
System.Diagnostics.Process.Start(info)
Return "hello"
End Function))
processTasks.Add(processTask)
Next
' if you are using a synchronous method
' Dim results() As String = Task.WhenAll(processTasks).GetAwaiter().GetResult()
' if you are using an async method
Dim results() As String = await Task.WhenAll(processTasks)
' Either use the string array as a whole, or
For Each result As String In results
' Do something with every individual result
Next
Note I've answered the question using C# because OP tagged the question with c#.
You should simply use Task:
List<Task<string>> processTasks = new List<Task<string>>();
for(int i = 0; i < 130; i ++)
{
Task<string> processTask = Task<string>
(
() =>
{
// System.Diagnostics.Process stuff goes here, you should return a string
// since it represents the output of the whole process
}
);
processTasks.Add(processTask);
Task.Run(processTask);
}
// Asynchronously wait for all tasks to finish
await Task.WhenAll(processTasks);
// Now after all tasks have finished, each stored task in "processTasks"
// will contain a task where their "Result" property is the string representing
// the whole process output

How can I move mailItem from 1 store to another

I'm trying to move a mail item form 1 store to another, using Outlook 2010 and C#
I'm gotten quite far, but not sure how I perform the 'move'. I'm assuming it's saveas and then delete
My attempt (code greatly reduced)
foreach (var mail in folder.Items)
{
//I am in the correct folder, and all I want to do is move all items to the 'inbox' of the store. I have already gotten the destination store and saved it as a variable called store
Microsoft.Office.Interop.Outlook.MailItem mailItem = (Microsoft.Office.Interop.Outlook.MailItem)mail; //got the item
mailItem.SaveAs(store.FilePath, Microsoft.Office.Interop.Outlook.OlSaveAsType.olMSG); // throws exception
mailItem.Delete();
}
I'm not sure if this is the best approach, but the line mailItem.SaveAs(store.FilePath, Microsoft.Office.Interop.Outlook.OlSaveAsType.olMSG); throws an exception:
The operation failed
I see no more detail other than that
The SaveAs method saves the Microsoft Outlook item to the specified path and in the format of the specified file type. If the file type is not specified, the MSG format (.msg) is used. So, the method is used to save items on the disk, not another store in Outlook.
You can use the Move method of the MailItem class to move a Microsoft Outlook item to a new folder. For example:
Sub MoveItems()
Dim myNameSpace As Outlook.NameSpace
Dim myInbox As Outlook.Folder
Dim myDestFolder As Outlook.Folder
Dim myItems As Outlook.Items
Dim myItem As Object
Set myNameSpace = Application.GetNamespace("MAPI")
Set myInbox = myNameSpace.GetDefaultFolder(olFolderInbox)
Set myItems = myInbox.Items
Set myDestFolder = myInbox.Folders("Personal Mail")
Set myItem = myItems.Find("[SenderName] = 'Eugene'")
While TypeName(myItem) <> "Nothing"
myItem.Move myDestFolder
Set myItem = myItems.FindNext
Wend
End Sub

Disable JIT debugging

This topic has been asked and answered in many questions and I did my due diligence but I just can't figure out why I am having the issue I have.
In testfailure.exe:
namespace testfailture
{
class Program
{
static void Main(string[] args)
{
try
{
throw new Exception("I want to report this in the parent window");
}
catch (Exception ex)
{
throw ex;
}
}
}
In test.exe:
internal void Execute(string packageVersion)
{
Process exeProcess = new Process();
exeProcess.StartInfo.FileName = "testfailure.exe";
exeProcess.StartInfo.RedirectStandardOutput = true;
exeProcess.StartInfo.UseShellExecute = false;
exeProcess.StartInfo.CreateNoWindow = true;
try
{
exeProcess.Start();
Console.WriteLine(exeProcess.StandardOutput.ReadToEnd());
}
catch (Exception ex)
{
throw ex;
}
}
When I run the program, I get the pop-up and wouldn't let the program proceed until an action is taken.
I thought this was due to JIT debugging so I did everything from:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/a52eb0ae-bcd8-4043-9661-d5fc3aa5167c/getting-rid-of-justintime-debugger?forum=vsdebug
That is one problem I have but ultimate what I want to do is subprocess reporting back the error (not console.writeline because I want to use that for reporting status) to the parent and display in parent's window.
Any thoughts?
By the way, I am using Visual Studio Professional 2012. Your help is much appreciated.
Thanks!
Edit #1----------------------------
My process fully expects everything to work but what I am trying to do is to code for unexpected failures. When fails, I want to have a good dialogue so I can easily and quickly detect and debug.
Throwing an unhandled exception cannot be passed to a parent process.
What you CAN do, however, is to write data to StandardError, and capture that in the parent process.
The example below is from MSDN:
Public Sub Main()
Dim args() As String = Environment.GetCommandLineArgs()
Dim errorOutput As String = ""
' Make sure that there is at least one command line argument.
If args.Length <= 1 Then
errorOutput += "You must include a filename on the command line." +
vbCrLf
End If
For ctr As Integer = 1 To args.GetUpperBound(0)
' Check whether the file exists.
If Not File.Exists(args(ctr)) Then
errorOutput += String.Format("'{0}' does not exist.{1}",
args(ctr), vbCrLf)
Else
' Display the contents of the file.
Dim sr As New StreamReader(args(ctr))
Dim contents As String = sr.ReadToEnd()
sr.Close()
Console.WriteLine("***** Contents of file '{0}':{1}{1}",
args(ctr), vbCrLf)
Console.WriteLine(contents)
Console.WriteLine("*****{0}", vbCrLf)
End If
Next
' Check for error conditions.
If Not String.IsNullOrEmpty(errorOutput) Then
' Write error information to a file.
Console.SetError(New StreamWriter(".\ViewTextFile.Err.txt"))
Console.Error.WriteLine(errorOutput)
Console.Error.Close()
' Reacquire the standard error stream.
Dim standardError As New StreamWriter(Console.OpenStandardError())
standardError.AutoFlush = True
Console.SetError(standardError)
Console.Error.WriteLine("{0}Error information written to ViewTextFile.Err.txt",
vbCrLf)
End If
End Sub

How to add an attachment to a test set in ALM OTA via c#?

I run the following code but nothing shows up in ALM:
AttachmentFactory attachmentFactory = (AttachmentFactory)tsTest.Attachments;
TDAPIOLELib.Attachment attachment = (TDAPIOLELib.Attachment)attachmentFactory.AddItem("test");
attachment.Post();
The AddItem method on the second line keeps asking for "object ItemData" but I have no idea what that is exactly. HP has such poor documentation that there is really nothing explaining it. Does anyone know how to programatically using c# add a file attachment to a test run in HP ALM?
After much pain and research I have found an answer. I'm sure there are other ways of accomplishing this that are more efficient but since HP's documentation is the worst on the planet this is the best I could come up with. If anyone has a better way I would LOVE to see it so please post it!
I hope this helps!
try
{
if (qcConn.Connected)
{
string testFolder = #"Root\YourFolder";
TestSetTreeManager tsTreeMgr = (TestSetTreeManager)qcConn.TestSetTreeManager;
TestSetFolder tsFolder = (TestSetFolder)tsTreeMgr.get_NodeByPath(testFolder);
AttachmentFactory attchFactory = (AttachmentFactory)tsFolder.Attachments;
List tsList = tsFolder.FindTestSets("YourTestNameHere", false, null);
foreach (TestSet ts in tsList)
{
TestSetFolder tstFolder = (TestSetFolder)ts.TestSetFolder;
TSTestFactory tsTestFactory = (TSTestFactory)ts.TSTestFactory;
List mylist = tsTestFactory.NewList("");
foreach (TSTest tsTest in mylist)
{
RunFactory runFactory = (RunFactory)tsTest.RunFactory;
Run run = (Run)runFactory.AddItem("NameYouWantDisplayedInALMRuns");
run.CopyDesignSteps();
//runResult just tells me if overall my test run passes or fails - it's not built in. It was my way of tracking things though the code.
if(runResult)
run.Status = "Failed";
else
run.Status = "Passed";
run.Post();
//Code to attach an actual file to the test run.
AttachmentFactory attachmentFactory = (AttachmentFactory)run.Attachments;
TDAPIOLELib.Attachment attachment = (TDAPIOLELib.Attachment)attachmentFactory.AddItem(System.DBNull.Value);
attachment.Description = "Attach via c#";
attachment.Type = 1;
attachment.FileName = "C:\\Program Files\\ApplicationName\\demoAttach.txt";
attachment.Post();
//Code to attach a URL to the test run
AttachmentFactory attachmentFactory = (AttachmentFactory)run.Attachments;
TDAPIOLELib.Attachment attachment = (TDAPIOLELib.Attachment)attachmentFactory.AddItem(System.DBNull.Value);
//Yes, set the description and FileName to the URL.
attachment.Description = "http://www.google.com";
attachment.Type = 2;
attachment.FileName = "http://www.google.com";
attachment.Post();
//If your testset has multiple steps and you want to update
//them to pass or fail
StepFactory rsFactory = (StepFactory)run.StepFactory;
dynamic rdata_stepList = rsFactory.NewList("");
var rstepList = (TDAPIOLELib.List)rdata_stepList;
foreach (dynamic rstep in rstepList)
{
if (SomeConditionFailed)
rstep.Status = "Failed";
else
rstep.Status = "Passed";
rstep.Post();
}
else
{
rstep.Status = "No Run";
rstep.Post();
}
}
}
}
}
}
I have done something similar, but in Python and against Test Steps, so even if I don't have code you can copy & paste it, this might point you to the right direction.
Instead of calling:
attachmentFactory.AddItem( filename )
Call the function with no parameters (or a null paramer, can't tell since I never used the OTA API with C#):
file = attachmentFactory.AddItem()
Now assign the file to the attachment item, and the rest of its properties:
file.Filename = "C:\\Users\\myUser\\just\\an\\example\\path" + fileName
file.Description = "File description"
file.Type=1
file.Post()
The type specifies it's a local file, and not an URL.
If anyone is wondering how to do that on the requirement-module, here is the code:
Req req = Globals.Connection.ReqFactory.Item(*ID*));
VersionControl versionControl = ((IVersionedEntity)req).VC as VersionControl;
versionControl.CheckOut(string.Empty);
AttachmentFactory attFac = req.Attachments;
Attachment att = (Attachment)attFac.AddItem(System.DBNull.Value);
att.Description = "*Your description here";
att.Type = (int)TDAPI_ATTACH_TYPE.TDATT_FILE; //for URL, change here
att.FileName = "*Your path including filename here*";
att.Post();
versionControl.CheckIn("*Your check-in comment here*");
No valuable information on Internet!
After some digging on OTA documentation I have found this:
AttachmentFactory attachmentFactory = (AttachmentFactory)TstTest.Attachments;
TDAPIOLELib.Attachment attachment = (TDAPIOLELib.Attachment)attachmentFactory.AddItem("demoAttach.txt");
attachment.Description = "Bug Sample Attachment";
attachment.Post();
IExtendedStorage exStrg = attachment.AttachmentStorage;
exStrg.ClientPath = "E:\\TestData";
exStrg.Save("demoAttach.txt", true);
actually, was in VB script form but I managed to transform in C#.
OTA reference:
'-----------------------------------------
'Use Bug.Attachments to
' get the bug attachment factory.
Set attachFact = bugObj.Attachments
'Add a new extended storage object,an attachment
' named SampleAttachment.txt.
Set attachObj = attachFact.AddItem("SampleAttachment.txt")
' Modify the attachment description.
attachObj.Description = "Bug Sample Attachment"
' Update the attachment record in the project database.
attachObj.Post
' Get the bug attachment extended storage object.
Set ExStrg = attachObj.AttachmentStorage
'Specify the location of the file to upload.
ExStrg.ClientPath = "D:\temp\A"
'-----------------------------------------
'Use IExtendedStorage.Save to
' upload the file.
ExStrg.Save "SampleAttachment.txt", True

Categories

Resources