C# SessionSwitchReason.SessionLock NOT triggering when machine is locked via Group Policy - c#

EDIT: The issue here wan't the fact it was locked via GP, it was that it was being run as a service under a service account and it didn't have access to the interactive desktop
I have a C# application that needs to check for when a user's session is locked, I'm using Microsoft.Win32.SystemEvents.SessionSwitch and this works fine when a user manually locks the machine.
The problem is that when the machine is locked via a group policy (User Configuration > Policies > Administrative Templates > Personalization > Screen saver timeout) the application doesn't pick up the switch.
Is there another way to check for a machine being locked? Or is there another way to lock machines via group policy that will be picked up by the application?
N.B. The application is running on windows 7 as a service with full admin rights
Here's my code, Thanks in advance!!! :)
public void OnStart(string[] args)
{
Microsoft.Win32.SystemEvents.SessionSwitch += new Microsoft.Win32.SessionSwitchEventHandler(SystemEvents_SessionSwitch);
}
void SystemEvents_SessionSwitch(object sender, Microsoft.Win32.SessionSwitchEventArgs e)
{
if (e.Reason == SessionSwitchReason.SessionLock)
{
//DO STUFF
}
}

I managed to resolve this by enabling 'Other Logon/Logoff Events' in Windows Event Viewer and searching for the lock and unlock events.
//Define strings for searching the eventlog.
string lockEvent = "4800";
string unlockEvent = "4801";
//Define the Eventlog source you want (in this case it's Security)
string LogSource = #"Security";
//Add these together to make the full query for Lock and Unlock
string LockQuery = " *[System/EventID=" + lockEvent + "]";
string UnlockQuery = "*[System/EventID=" + unlockEvent + "]";
//Return true if there is any locked events found.
private bool CheckForLock()
{
//Create Eventlog Reader and Query
var elQuery = new EventLogQuery(LogSource, PathType.LogName, LockQuery);
var elReader = new System.Diagnostics.Eventing.Reader.EventLogReader(elQuery);
//Create a list of Eventlog records and add the found records to this
List<EventRecord> eventList = new List<EventRecord>();
for (EventRecord eventInstance = elReader.ReadEvent();
null != eventInstance; eventInstance = elReader.ReadEvent())
{
eventlist.add(eventInstance);
}
if(eventList.count > 0)
{
return true;
}
else
{
return false;
}
}
N.B. This will check all the event log, so you need to put a qualifier on how far into the past you want to bee looking.
If you check for lock/unlock sessions every ten seconds, you only want to deal with an EventRecord if it's from the same time period.
You can access the eventlist.TimeCreated value to do something like...
if (eventInstance.TimeCreated > DateTime.Now - TimeSpan.FromSeconds(10))
{
eventList.Add(eventInstance);
}
Is it elegant? No. Does it work? Yes.

Related

Get applocker status of .exe file

I have developed a launcher that updates/run our software in other companies enviroment. Most of the time the company approves the launcher but forgets about the actual application.
Currently im using Process.Start(procStart) to start the application, but that silently fails if applocker blocks the application.
So i was wondering if someone reliable way of detecting if applocker is active, and when it blocks my application( So i can provide a proper error message).
When the error happens my application seems idle to the user, a memory dump shows this:
Code note:
There is no exception handling or suppression of exceptions. If the launcher crashes i would expect to see it in the eventlog.
Added code:
private void StartzzzDesktop(int value)
{
var rel = Settings.zzzDesktopStore.GetReleaseInfo(Settings.ConnectionDetails.zzzDesktopID);
var proc = CreateProccess(rel);
if (proc == null)
{
Settings.LastError = zzzLauncherError.FatelErrorStartzzz;
Settings.EventManager.TriggerEvent(zzzDesktopStatus.FatalError);
return;
}
Logger.Log(EventLogEntryType.Information, $"Started zzz desktop and got PID {proc.Id} from {rel.GenerateExtrationPath()}");
Settings.EventManager.TriggerEventSync(zzzDesktopStatus.DeleteOldReleases);
Settings.EventManager.TriggerEvent(zzzDesktopStatus.ReleaseBackgroundWorkers);
GC.Collect();
var remoteStatus = new GetRemotezzzWebStatus();
while (!proc.HasExited)
{
Thread.Sleep(1000);
if(!remoteStatus.IsRemoteVersionCompatible())
{
proc.Kill();
Logger.Log(EventLogEntryType.Information, $"Detected that the remote website is no longer compatible with current runnign version, and we are killing desktop.");
}
}
if(proc.ExitCode != 0)
{
Settings.zzzDesktopStore.Delete(rel);
Logger.Log(EventLogEntryType.Warning, $"zzz exited with a none zero exit code ({proc.ExitCode}), the local cached installation will be deleted");
}
else
Logger.Log(EventLogEntryType.Information, $"zzz exited in a normal way with exitcode {proc.ExitCode}, running for {(DateTime.Now - proc.StartTime).ToString()} ");
CloseDown();
}
internal Process CreateProccess(zzzDesktopInfo release)
{
release = GetReleaseInfo(release.ID);
string pathzzzExe = Path.Combine(release.GenerateExtrationPath(), "zzz.exe");
var verifyStatus = UtilsVerifyFile.Verify(pathzzzExe);
if ( !File.Exists(pathzzzExe) || !verifyStatus.Verified)
{
Logger.Log(EventLogEntryType.Error, "Found zzz.exe in temp folder, but the certificate did not pass verification");
foreach (var logentry in verifyStatus.Logs)
Logger.Log(EventLogEntryType.Error, "Certificate verification log: " + logentry);
MarkDatabaseForPurge();
return null;
}
// Removed enterprise spesific code.
var procStart = new ProcessStartInfo();
procStart.FileName = pathzzzExe;
if (Settings.ConnectionDetails.zzzLoginToken != Guid.Empty )
{
procStart.Arguments = "/RefreshToken:" + Settings.ConnectionDetails.zzzLoginToken.ToString();
}
var process = Process.Start(procStart);
return process;
}

Can't access the network drive while running the windows service

I'm trying to create a windows service in C# that will copy all the files from a network drive and paste it into a local drive (let's say in C drive). When I run the test case, the program runs successfully but when I install and run the windows service, the 'Access is denied' error comes in the log file.
I tried Map Network Drive (API) solution but that solution didn't work. either.
Here's the sample code that I've used to get all the files from a network drive and paste it into the local drive folder
Service1.cs
public partial class Service1 : ServiceBase
{
private Timer _timer;
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
try
{
DoWork();
}
catch (Exception e)
{
WriteErrorLog(e);
}
}
private void DoWork()
{
_timer = new Timer();
_timer.Interval = 5000;
_timer.Enabled = true;
_timer.Elapsed += _timer_Elapsed;
Update();
}
private void Update()
{
RevidAddinController.Update_AutodeskAddinFolder_With_ArchcorpUpdatedAddinFiles(Configuration.AutodeskVersion, Configuration.AutodeskRevitAddinFolderPath);
}
private void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
Update();
}
private void WriteErrorLog(Exception ex)
{
StreamWriter sw = null;
try
{
sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "\\Logfile.txt", true);
sw.WriteLine(DateTime.Now.ToString() + " ; " + ex.Source.ToString().Trim() + "; " + ex.Message.ToString().Trim());
sw.Flush();
sw.Close();
}
catch
{
}
}
protected override void OnStop()
{
}
}
RevidAddinController.cs
public static class RevidAddinController
{
public static IEnumerable<AddinStatus> Update_AutodeskAddinFolder_With_ArchcorpUpdatedAddinFiles(List<string> autoDeskVersion, string addinInstallationPath)
{
var networkDrive = ActivateNetworkDrive();
var allAutodeskVersionPath = Util.GetAllAutodeskAddinLibraryFolderPaths(autoDeskVersion, addinInstallationPath);
List<FileData> latestArchcorpAddins = new List<FileData>();
foreach (var autodeskAddinFolder in allAutodeskVersionPath)
{
var archorpAddinFiles = Util.GetAllExternalRevitAddinFilesFromArchcorpAddinFolderPath(Configuration.ArchcorpAddinFolderPath);
var autodeskAddinFiles = Util.GetAllExternalRevitAddinFilesLocationFromAutodeskAddinFolderPath(autodeskAddinFolder);
var latestAddins = Util.GetUpdatedRevitAddinFromArchcorpFolderPath(autodeskAddinFolder, archorpAddinFiles, autodeskAddinFiles)
.Where(addin => !addin.FileName.Contains(Configuration.DeleteAddinNamePrefix));
latestArchcorpAddins.AddRange(latestAddins);
}
List<AddinStatus> addinCopyStatus = new List<AddinStatus>();
foreach (var autodeskAddinPath in allAutodeskVersionPath)
{
foreach (var newArchcorpAddin in latestArchcorpAddins)
{
addinCopyStatus.Add(Util.InstallNewAddinFile(newArchcorpAddin, autodeskAddinPath));
}
}
return addinCopyStatus;
}
/// <summary>
/// Map the network drive path
/// </summary>
/// <returns></returns>
public static NetworkDrive ActivateNetworkDrive()
{
NetworkDrive oNetDrive = new aejw.Network.NetworkDrive();
try
{
oNetDrive.LocalDrive = "O:";
oNetDrive.ShareName = #"\\acdxbfs1\Organisation";
oNetDrive.Force = true;
oNetDrive.Persistent = true;
oNetDrive.MapDrive();
}
catch (Exception err)
{
throw err;
}
return oNetDrive;
}
}
The complete code can be found on the gist here. Would really appreciate if someone reviews the code and provides any feedback/solution to this problem.
Running a service under the default Local System Account, will have no concept of the share. These are set up under user accounts.
Your 2 options
Run your service under a User Account which has those shares mapped
Access your share via and ip address instead of the drive letter. However, you will need to set the file/folder permissions accordingly.
The service does run as Local System (as previously named). If you have a mapped network drive to a local drive letter, the service cannot use it (because a mapped network drive is always only mapped for the user context, not the whole computer/system). However the service can access the share by UNC \\server\share. You can view the UNC path if you only have a mapped network drive by typing 'net use' inside a command prompt.
If you run your program as a user Windows does automatically authenticate you at the remote share (if not already done by adding a mapped network drive). Therefor Local System is the computer account you need to set the access permissions of the target share to the computername eg workstation1$ (only available inside a domain cause a workgroup does not know the other computers). This has to be done for the file permissions and the share permissions because both are independent and can restrict you from the access.
As an alternative you can authenticate at the remote network share with an user and password - there is an excellent thread in stackoverflow which you can find here which does show how you can achieve this.
Naturally you can also set the service to a user/password in the services manager (services.msc - double click your service and go to the logon tab) who has access to the share. By doing this, the user will be granted the 'login as service' permission which is necessary for this.
If the network file is shared with the local system account then you need to Log In as "Local System Account",
The advantage of running your services as the "Local System account" is that the service has complete unrestricted access to local resources.
But there are some disadvantages also, so be careful to not install unauthorized services as service gets full unrestricted access. Also if the service has some bugs it may lead to performance issues.

Windows Event Log

I am developing an app to capture event logs (security) from multiple Windows systems. I have a handler to EntryWritten. I am able to map most fields from the Event Viewer to the EntryWrittenEventArgs entry in .net. However, I cannot seem to find the mappings for the Level, OpCode and Task Category fields which show up in Event Viewer. Any ideas on how I get this in vb.net or c#? Thanks
The EventLog class in the System.Diagnostics namespace does not contain fields for Level, OpCode or Task. There is, however, the EventRecord class in the System.Diagnostics.Eventing.Reader namespace which is capable of returning those fields. Note that this namespace is mainly used for retrieving event logs from a remote machine. Even though you could use it to get logs on the local machine as well, it opens a local pipe to the system, which makes it slower than the EventLog class. If you really need to access those fields though, this is how this class is generally used:
private void LoadEventLogs()
{
List<EventRecord> eventLogs = new List<EventRecord>();
EventLogSession session = new EventLogSession();
foreach (string logName in session.GetLogNames())
{
EventLogQuery query = new EventLogQuery(logName, PathType.LogName);
query.TolerateQueryErrors = true;
query.Session = session;
EventLogWatcher logWatcher = new EventLogWatcher(query);
logWatcher.EventRecordWritten +=
new EventHandler<EventRecordWrittenEventArgs>(LogWatcher_EventRecordWritten);
try
{
logWatcher.Enabled = true;
}
catch (EventLogException) { }
// This is how you'd read the logs
//using (EventLogReader reader = new EventLogReader(query))
//{
// for (EventRecord eventInstance = reader.ReadEvent(); eventInstance != null; eventInstance = reader.ReadEvent())
// {
// eventLogs.Add(eventInstance);
// }
//}
}
}
And the LogWatcher_EventRecordWritten event handler:
private void LogWatcher_EventRecordWritten(object sender, EventRecordWrittenEventArgs e)
{
var level = e.EventRecord.Level;
var task = e.EventRecord.TaskDisplayName;
var opCode = e.EventRecord.OpcodeDisplayName;
// Other properties
}
Note that I wrapped the logWatcher.Enabled = true; statement in a try-catch block, because not all sources allow entry-written listeners (security should work fine). The commented-out section shows you an example of reading all the logs, if you need it.

Programmatically get site status from IIS, gets back COM error

I am trying to programmatically get my site status from IIS to see if it's stopped, but I kept getting the following error,
The object identifier does not represent a valid object. (Exception from HRESULT: 0x800710D8)
The application is using ServerManager Site class to access the site status. Here is the code,
//This is fine, gets back the site
var serverManager = new Microsoft.Web.Administration.ServerManager(ConfigPath);
var site = serverManager.Sites.FirstOrDefault(x => x.Id == 5);
if (site == null) return;
var appPoolName = site.Applications["/"].ApplicationPoolName;
//error!
var state = site.State;
I've test with static site to isolate the issue, making sure that the site is up and running, all configuration are valid, point to the valid application pool...etc.
Let me know if you need more details. Is it the COM thing?
I figured out where the problem is. Basically, there are two parts to the Server manager, the first part of the server manager allows you to read site details from configuration file, which is what I've been doing above. The problem with that is you will only able get the information that's in file and site state is not part of it.
The second part of the Server Manager allows you to connect to the IIS directly and it does this by interacting with the COM element. So what I should be doing is this:
ServerManager manager= ServerManager.OpenRemote("testserver");
var site = manager.Sites.First();
var status = site.State.ToString() ;
I had a similar problem but mine was caused by the delay needed to activate the changes from the call to CommitChanges on the ServerManager object. I found the answer I needed here:
ServerManager CommitChanges makes changes with a slight delay
It seems like polling is required to get consistent results. Something similar to this solved my problem (I got the exception when accessing a newly added application pool):
...
create new application pool
...
sman.CommitChanges();
int i = 0;
const int max = 10;
do
{
i++;
try
{
if (ObjectState.Stopped == pool.State)
{
write_log("Pool was stopped, starting: " + pool.Name);
pool.Start();
}
sman.CommitChanges();
break;
}
catch (System.Runtime.InteropServices.COMException e)
{
if (i < max)
{
write_log("Waiting for IIS to activate new config...");
Thread.Sleep(1000);
}
else
{
throw new Exception(
"CommitChanges timed out efter " + max + " attempts.",
e);
}
}
} while (true);
...

Listening to Events in the calendar from more than one person using EWS API

Simply I would like to receive a notification every time someone added a new appointment or made any changes on what he/she has.
The only way I know how to do it , is by using
service.SubscribeToStreamingNotifications
but the problem here that it only listens to the account that the service is bound to like in this way
var service = new ExchangeService(ExchangeVersion.Exchange2010_SP2)
{
Credentials = new WebCredentials(userName, password)
};
service.SubscribeToStreamingNotifications(new FolderId[]
{
WellKnownFolderName.Calendar
}, EventType.FreeBusyChanged, EventType.Deleted);
I have solved this problem by creating a list of services each service is bounded to different user and the application should listen to each of them.
The problem with this way is that I need to have the password of each account I wont to listen to its events, which is not possible in real world.
so is there any way to deal with that ?
I have solved this problem, by creating a list of services, all the services are a clone of the main ExchangeService, with the same credentials for the admin account, but they are impersonated to the other accounts.
NOTE: You need to setup the server so it allows impersonation.
private void ImpersonateUsers(ICollection<string> userSmtps)
{
if (userSmtps != null)
if (userSmtps.Count > 0)
{
foreach (var userSmtp in userSmtps)
{
if (_services.ContainsKey(userSmtp)) continue;
var newService = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
try
{
var serviceCred = ((System.Net.NetworkCredential)(((WebCredentials)(_services.First().Value.Credentials)).Credentials));
newService.Credentials = new WebCredentials(serviceCred.UserName, serviceCred.Password, serviceCred.Domain);
newService.AutodiscoverUrl(serviceCred.UserName + "#" + serviceCred.Domain, RedirectionUrlValidationCallback);
newService.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, userSmtp);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
_services.Add(userSmtp, newService);
}
}
}
Where userSmtps is a list of the email addresses I want to impersonate and _services is the dictionary of services where the first member is the main service.
you will have to create a service instance per user. There is no way to subscribe to other users folder.
But instead of StreamingNotifications you can use Pull and Push-Subscriptions too.
Something like this:
List<FolderId> folders = new List<FolderId>();
folders.Add(new FolderId(WellKnownFolderName.Calendar));
PullSubscription subscription = = service.SubscribeToPullNotifications(folders, 1440, watermark, EventType.Created, EventType.Deleted, EventType.Modified, EventType.Moved, EventType.NewMail);
Some time later....
GetEventsResults currentevents = m_subscription .GetEvents();

Categories