Event log API missing entries - c#

I'm trying to retrieve the most recent events from an event log. I found this answer Read Event Log From Newest to Oldest but it involves loading the whole log which I wish to avoid.
So I tried going to the end of the log first likes this:
var eventLog = EventLog.GetEventLogs().OfType<EventLog>().Single(el => el.Log == "Security");
int logSize = eventLog.Entries.Count;
var lastScanTime = DateTime.Now.AddDays(-1),
for (int i = logSize - 1; i >= 0; i--)
{
var entry = eventLog.Entries[i];
if (entry.TimeWritten <= lastScanTime)
{
break;
}
entry.Dump();
}
This works for a while, but if I keep running it I start to get IndexOutOfRangeExceptions
It seems that the eventLog.Entries.Count no longer matches the number of entries, which you can check with the followiog code:
var eventLog = EventLog.GetEventLogs().OfType<EventLog>().Single(el => el.Log == "Security");
int size = eventLog.Entries.Count.Dump();
int size2 = eventLog.Entries.Cast<EventLogEntry>().ToList().Count.Dump();
The two size values return different values. Entries.Countkeeps growing but the number of entries in the list stops increasing. The count also matches what I see in the Windows Event Log viewer.
Its like something in the .net API breaks and no more events are available.
Anybody seen this before or have any fixes to get this approach to work.
Edit: I also found this A mystery IndexOutOfRange Exception recurring when reading from a full EventLog which seems like a similar problem but no solution. I can't use events because I may be looking to activity on the machine before my software is installed.
Edit: If I catch exceptions and put the results into a list I get a different number of events again:
var results = new List<EventLogEntry>();
for (int i = eventLog.Entries.Count; i >= 0; i--)
{
try
{
var entry = eventLog.Entries[i];
{
results.Add(entry);
}
}
catch (Exception ex) { }
}
It has a lower count than Entries.Count but more recent logs entries than Entries does which seems to have just stopped at a certain point.

In case anyone else has the same issue it seems to be a result of not always disposing of the EventLog object after reading from file.
Once this happens it seems to break or corrupt the file in some way any you can't get more recent data until you clear the file out.

Related

StackExchange.Exceptional Get All Errors is too slow

I am trying to quickly load the most recent exceptions using ErrorStore in StackExchange.Exceptional in C#.
Is there any way I can get all of the exceptions since a recent date without having to load and sort all of the exceptions? How can I speed up my code?
My current code gets the 100 most recent exceptions, but it is very slow.
string[] applications = new[] {loc1, loc2};
var errors = new List<Error>();
applications.ForEach(app => ErrorStore.Default.GetAll(errors, app));
return errors.OrderByDescending(m => m.CreationDate).Take(100);
Here is the documentation for ErrorStore.
Your code is probably slow because of your third line of code
applications.ForEach(app => ErrorStore.Default.GetAll(errors, app));
Here is ErrorStore.GetAll
public int GetAll(List<Error> errors, string applicationName = null)
{
if (_isInRetry)
{
errors.AddRange(WriteQueue);
return errors.Count;
}
try { return GetAllErrors(errors, applicationName); }
catch (Exception ex) { BeginRetry(ex); }
return 0;
}
And here is one implementation of GetAllErrors
protected override int GetAllErrors(List<Error> errors, string applicationName = null)
{
using (var c = GetConnection())
{
errors.AddRange(c.Query<Error>(#"
Select Top (#max) *
From Exceptions
Where DeletionDate Is Null
And ApplicationName = #ApplicationName
Order By CreationDate Desc", new { max = _displayCount, ApplicationName = applicationName.IsNullOrEmptyReturn(ApplicationName) }));
}
return errors.Count;
}
The sql query in GetAllErrors will select all exceptions, not just the top 100. Suppose there are n total exceptions per application, and two applications. Thus, your third line of code would have to run in f(n) = 2n time.
To speed up your program, you can write a method similar to GetAllErrors, but modify the sql query to select only 100 exceptions from the error database, so even if there are billion exceptions, the code will finish after finding the first 100 that match.
You could also move the code to search for all application names to sql so you would not need to order by the creation date again.

Getting a list of all users via Valence

I am trying to get a list of all users in our instance of Desire2Learn using a looping structure through the bookmarks however for some reason it continuously loops and doesn't return. When I debug it it is showing massive amounts of users (far more than we have in the system as shown by the User Management Tool. A portion of my code is here:
public async Task<List<UserData>> GetAllUsers(int pages = 0)
{
//List<UserData> users = new List<UserData>();
HashSet<UserData> users = new HashSet<UserData>();
int pageCount = 0;
bool getMorePages = true;
var response = await Get<PagedResultSet<UserData>>("/d2l/api/lp/1.4/users/");
var qParams = new Dictionary<string, string>();
do
{
qParams["bookmark"] = response.PagingInfo.Bookmark;
//users = users.Concat(response.Items).ToList<UserData>();
users.UnionWith(response.Items);
response = await Get<PagedResultSet<UserData>>("/d2l/api/lp/1.4/users/", qParams);
if (pages != 0)
{
pageCount++;
if (pageCount >= pages)
{
getMorePages = false;
}
}
}
while (response.PagingInfo.HasMoreItems && getMorePages);
return users.ToList();
}
I originally was using the List container that is commented out but just switched to the HashSet to see if I could notice if duplicates where being added.
It's fairly simple, but for whatever reason it's not working. The Get<PagedResultSet<UserData>>() method simply wraps the HTTP request logic. We set the bookmark each time and send it on.
The User Management Tool indicates there are 39,695 users in the system. After running for just a couple of minutes and breaking on the UnionWith in the loop I'm showing that my set has 211,800 users.
What am I missing?
It appears that you’ve encountered a defect in this API. The next course of action is for you to have your institution’s Approved Support Contact open an Incident through the Desire2Learn Helpdesk. Please make mention in the Incident report that Sarah-Beth Bianchi is aware of the issue, and I will work with our Support team to direct this issue appropriately.

Delay in Active Directory user creation when using System.DirectoryServices.AccountManagement

I am creating accounts and setting properties on them using System.DirectoryServices.AccountManagement in .NET 4.5. One of the requirements is that the group membership (including Primary Group) be copied from a template account. The code includes the following:
foreach (var group in userPrincipal.GetGroups()) {
var groupPrincipal = (GroupPrincipal) #group;
if (groupPrincipal.Sid != templatePrimaryGroup.Sid) {
groupPrincipal.Members.Remove(userPrincipal);
groupPrincipal.Save();
}
}
This works about 90% of the time. The rest of the time, it fails with:
System.DirectoryServices.DirectoryServicesCOMException was
unhandled HResult=-2147016656 Message=There is no such object on
the server.
Source=System.DirectoryServices ErrorCode=-2147016656
ExtendedError=8333 ExtendedErrorMessage=0000208D: NameErr:
DSID-03100213, problem 2001 (NO_OBJECT), data 0, best match of:
'OU=Whatever,DC=domain,DC=local`
on the GetGroups call. My guess is that there is a race condition of some sort with the user not being fully created before I next go to access it. I know from diagnostic logging that I am going against the same domain controller each time (it's using the same PrincipalContext so that matches my expectation) so it's not a replication issue.
Is my guess accurate? Is there a good way to handle this? I could just throw in a Sleep but that seems like a cop-out at best and fragile at worst. So what is the right thing to do?
Try something like:
int maxWait = 120;
int cnt = 0;
bool usable = false;
while (usable == false && cnt < maxWait)
{
try
{
foreach (var group in userPrincipal.GetGroups())
{
var groupPrincipal = (GroupPrincipal)#group;
if (groupPrincipal.Sid != templatePrimaryGroup.Sid)
{
groupPrincipal.Members.Remove(userPrincipal);
groupPrincipal.Save();
}
}
usable = true;
break;
}
catch
{
System.Threading.Thread.Sleep(500);
}
}
if (usable)
//All okay
;
else
//Do something
;
This way you can try for "a while". If it works good, if not do something like log an error, so you can run a fix-it script later.

Can not get MSMQ Com to find my Queue

I am trying to get a count of messages in my MSMQ. I have found this code on the internet (many times):
// setup the queue management COM stuff
MSMQManagement _queueManager = new MSMQManagement();
object machine = "MyLaptopComputer";
object path = #"DIRECT=OS:MyLaptopComputer\PRIVATE$\MyQueue";
_queueManager.Init(ref machine, ref path);
Console.WriteLine(_queueManager.MessageCount);
Marshal.ReleaseComObject(_queueManager);
Every time I get to _queueManager.Init it fails with this error:
The queue path name specified is invalid.
I have checked (and double checked) my queue name to see if that is wrong. I have tried different queues, different machines, running remote, running local... Nothing works.
I have also tried variations on the code above. For example I have tried:
_queueManager.Init("MyLaptopComputer", #"DIRECT=OS:MyLaptopComputer\PRIVATE$\MyQueue");
The queues are used with NServiceBus and function just fine when I use NServiceBus to access them.
Does anyone have an Idea on how I can get this to work?
I think the problem is the error you're getting is a little misguiding. MSMQManagement.Init takes 3 parameters. They're all optional which is why in other languages (like VB) you'll sometimes see it called with only 2 parameters.
There is a CodeProject project that shows how to do what you're doing in C#:
private int GetMessageCount(string queueName)
{
int count = 0;
try
{
MSMQ.MSMQManagement mgmt = new MSMQ.MSMQManagement();
MSMQ.MSMQOutgoingQueueManagement outgoing;
String s = "YOURPCNAME";
Object ss = (Object)s;
String pathName = queueName;
Object pn = (Object)pathName;
String format = null;
Object f = (Object)format;
mgmt.Init(ref ss , ref f, ref pn);
outgoing = (MSMQ.MSMQOutgoingQueueManagement)mgmt;
count = outgoing.MessageCount;
}
catch (Exception ee)
{
MessageBox.Show(ee.ToString());
}
return count;
}
It might provide a better starting point.
Turns out it was a combination of issues. The biggest being that I needed to use FormatName not path name.
_queueManager.Init("MyComputer", null, #"DIRECT=OS:MyComputer\PRIVATE$\MyQueue");
Also, it will throw an exception if the queue is empty...
Got to love COM interfaces. :)

is there a better way to handle RPC_E_CALL_REJECTED exceptions when doing visual studio automation?

this is what I'm currently doing:
protected void setupProject()
{
bool lbDone = false;
int liCount = 0;
while (!lbDone && liCount < pMaxRetries)
{
try
{
pProject.ProjectItems.Item("Class1.cs").Delete();
lbDone = true;
}
catch (System.Runtime.InteropServices.COMException loE)
{
liCount++;
if ((uint)loE.ErrorCode == 0x80010001)
{
// RPC_E_CALL_REJECTED - sleep half sec then try again
System.Threading.Thread.Sleep(pDelayBetweenRetry);
}
}
}
}
now I have that try catch block around most calls to the EnvDTE stuff, and it works well enough. The problem I have is when I to loop through a collection and do something to each item once.
foreach(ProjectItem pi in pProject.ProjectItems)
{
// do something to pi
}
Sometimes I get the exception in the foreach(ProjectItem pi in pProject.ProjectItems) line.
Since I don't want to start the foreach loop over if I get the RPC_E_CALL_REJECTED exception I'm not sure what I can do.
Edit to answer comment:
Yes I'm automating VS from another program and yes I usually am using VS for something else at the same time. We have an application that reads an xml file then generates around 50 VS solutions based on the xml file. This usually takes a couple of hours so I try to do other work while this is happening.
There is a solution on this MSDN page: How to: Fix 'Application is Busy' and 'Call was Rejected By Callee' Errors. It shows how to implement a COM IOleMessageFilter interface so that it will automatically retry the call.
First, Hans doesn't want to say so but the best answer to "how to do this" is "don't do this". Just use separate instances of visual studio for your automation and your other work, if at all possible.
You need to take your problem statement out somewhere you can handle the error. You can do this by using in integer index instead of foreach.
// You might also need try/catch for this!
int cProjectItems = pProject.ProjectItems.Length;
for(iProjectItem = 0; iProjectItem < cProjectItems; iProjectItem++)
{
bool bSucceeded = false;
while(!bSucceeded)
{
try{
ProjectItem pi = pProject.ProjectItems[iProjectItem];
// do something with pi
bSucceeded = true;
}catch (System.Runtime.InteropServices.COMException loE)
{
liCount++;
if ((uint)loE.ErrorCode == 0x80010001) {
// RPC_E_CALL_REJECTED - sleep half sec then try again
System.Threading.Thread.Sleep(pDelayBetweenRetry);
}
}
}
}
I didn't have much luck with the recommended way from MSDN, and it seemed rather complicated. What I have done is to wrap up the re-try logic, rather like in the original post, into a generic utility function. You call it like this:
Projects projects = Utils.call( () => (m_dteSolution.Projects) );
The 'call' function calls the function (passed in as a lambda expression) and will retry if necessary. Because it is a generic function, you can use it to call any EnvDTE properties or methods, and it will return the correct type.
Here's the code for the function:
public static T call<T>(Func<T> fn)
{
// We will try to call the function up to 100 times...
for (int i=0; i<100; ++i)
{
try
{
// We call the function passed in and return the result...
return fn();
}
catch (COMException)
{
// We've caught a COM exception, which is most likely
// a Server is Busy exception. So we sleep for a short
// while, and then try again...
Thread.Sleep(1);
}
}
throw new Exception("'call' failed to call function after 100 tries.");
}
As the original post says, foreach over EnvDTE collections can be a problem as there are implicit calls during the looping. So I use my 'call' function to get the Count proprty and then iterate using an index. It's uglier than foreach, but the 'call' function makes it not so bad, as there aren't so many try...catches around. For example:
int numProjects = Utils.call(() => (projects.Count));
for (int i = 1; i <= numProjects; ++i)
{
Project project = Utils.call(() => (projects.Item(i)));
parseProject(project);
}
I was getting the same error using C# to read/write to Excel. Oddly, it worked in debug mode but not on a deployed machine. I simply changed the Excel app to be Visible, and it works properly, albeit about twice as slow. It is annoying to have an Excel app open and close dynamically on your screen, but this seems to be the simplest work-around for Excel.
Microsoft.Office.Interop.Excel.Application oApp = new ApplicationClass();
oApp.Visible = true;
oApp.DisplayAlerts = false;

Categories