Reputation: 21
I wrote a C# app which takes screenshot every 5 mins a saves it to server. It is timer, 2 threads, few methods(ping srv, check folder, take screenshot etc.).
As process (exe) it runs great, but I need to install it as service. I am installing it trough installutil (Framework service installer).
My problem is, that when it is installed as service, it doesn't take screenshots. Pull out some, when stopping the service. Not the right resolution and black.
I assume, that the executive code is misplaced (see main). I don't know where to put it, since I cannot have more than one main method. Please help.
Code of main application:
I've deleted some not important code.
using System;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
//using System.Timers;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.ComponentModel;
using System.Configuration.Install;
namespace LogWriterService
{
static class Program
{
public static int TimeO = 5; // zpoždění časovače v minutách
private static bool Online;
private static bool active = false;
public static String GetIP()
{
// ...
// returns IP like xxx.xxx.xxx.xxx
// ...
}
// Test dostupnosti serveru
public static bool PingTest()
{
// ...
// return true if server is reachable
// ...
}
/*
* Z Windows.Forms _ screenů získá obrazová data, která uloží na
* server jako ../[IP]/[současný_systémový_čas].jpg
*/
public static void ScreenShot() //Bitmap
{
Int64 CurrTime = Int64.Parse(DateTime.Now.ToString("yyyyMMddhhmmss")); //yyyyMMddhhmmss
Rectangle bounds = Rectangle.Empty;
foreach (Screen s in Screen.AllScreens)
bounds = Rectangle.Union(bounds, s.Bounds);
Bitmap screenShotBMP = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb); // PixelFormat.Format32bppArgb
Graphics screenShotGraphics = Graphics.FromImage(screenShotBMP);
screenShotGraphics.CopyFromScreen(bounds.X, bounds.Y,
0, 0, bounds.Size, CopyPixelOperation.SourceCopy);
string path = null; //"D:/TEMP/" + CurrTime + ".jpg"; // GetIP()
// Ukládání obrázků do dočasné složky a přesun, pokud se připojí
if (PingTest() == true)
{
path = "//10.0.0.10/Upload/screen/test/" + GetIP() + "/" + CurrTime + ".jpg";
string path2 = "//10.0.0.10/Upload/screen/test/" + GetIP() + "/";
Online = true;
if (Directory.Exists(path2))
{
MoveCached();
}
else
{
Console.WriteLine("Online slozka neni dostupna.");
}
} else {
Console.WriteLine("Caching .. ");
path = "C:/TEMP/" + CurrTime + ".jpg"; // "C:/TEMP/" + GetIP() + "/" + CurrTime + ".jpg"
string LPath = @"c:\TEMP";
if (!Directory.Exists(LPath))
{
DirectoryInfo di = Directory.CreateDirectory(LPath);
di.Attributes = FileAttributes.Directory | FileAttributes.Hidden;
Console.WriteLine("Lokalni slozka neexistuje. Vytvarim ..");
}
Online = false;
}
screenShotBMP.Save(path, ImageFormat.Jpeg); // C:\\test\\test.jpg
screenShotGraphics.Dispose();
screenShotBMP.Dispose();
return; //screenShotBMP;
}
/*
* Přesune cache soubory (za dobu offline) na server
*/
public static void MoveCached()
{
// ...
// after conect, move localy saved screenshots to server
// ...
}
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
// Vytvoří událost signalizující hranici odpočtu ve
// zpětném volání časovače
AutoResetEvent autoEvent = new AutoResetEvent(false);
// Počet průchodů timeru
StatusChecker statusChecker = new StatusChecker(Timeout.Infinite); // 1440
// Vytvoří odvozeného delegáta, který vyvolá metody pro časovač
TimerCallback tcb = statusChecker.CheckStatus;
// Create a timer that signals the delegate to invoke
// CheckStatus after one second, and every 1/4 second
// thereafter.
System.Threading.Timer stateTimer = new System.Threading.Timer(tcb, autoEvent, 1000, TimeO * 1000); // TimeO * 1000 * 60 * 12 * 5, 250
// When autoEvent signals, change the period to every
// 1/2 second.
autoEvent.WaitOne(15000, false);
stateTimer.Change(0, TimeO * 1000 * 60); // TimeO * 1000 * 60
Console.WriteLine("menim poprve..");
// When autoEvent signals the second time, dispose of
// the timer.
autoEvent.WaitOne(Timeout.Infinite, false);
stateTimer.Change(0, TimeO * 1000 * 60); // TimeO * 1000 * 60
Console.WriteLine("menim podruhe..");
//stateTimer.Dispose();
// Garbage collector
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
class StatusChecker
{
private int invokeCount;
private int maxCount;
Int64 CurrTime = Int64.Parse(DateTime.Now.ToString("hh")); // screeny od 6:00 do 16:00
public StatusChecker(int count)
{
invokeCount = 0;
maxCount = count;
}
// Tato metoda je volána delegátem časovače
public void CheckStatus(Object stateInfo)
{
AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
//if ((CurrTime > 6) & (CurrTime < 16)) // 16
//{
LogWriterService.Program.ScreenShot();
Console.WriteLine("ScreenShot ..");
//}
Console.WriteLine("{0} Kontroluji stav {1,2}.",
DateTime.Now.ToString("h:mm:ss.fff"),
(++invokeCount).ToString());
if (invokeCount == maxCount)
{
// Resetuje čítač a signál Main.
invokeCount = 0;
autoEvent.Set();
}
// Garbage collector
GC.Collect();
Console.WriteLine("Paměť uvolněna .. \n");
}
}
Code of service:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
namespace LogWriterService
{
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
EventLog.WriteEntry("Sluzba screenshot se spustila.");
}
protected override void OnStop()
{
EventLog.WriteEntry("Sluzba screenshot se zastavila.");
}
}
}
I thing the problem may be here:
public static void CreateProcessAsUser()
{
IntPtr hToken = WindowsIdentity.GetCurrent().Token;
IntPtr hDupedToken = IntPtr.Zero;
ProcessUtility.PROCESS_INFORMATION pi = new ProcessUtility.PROCESS_INFORMATION();
try
{
ProcessUtility.SECURITY_ATTRIBUTES sa = new ProcessUtility.SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
bool result = ProcessUtility.DuplicateTokenEx(
hToken,
ProcessUtility.GENERIC_ALL_ACCESS,
ref sa,
(int)ProcessUtility.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)ProcessUtility.TOKEN_TYPE.TokenPrimary,
ref hDupedToken
);
if (!result)
{
throw new ApplicationException("DuplicateTokenEx failed");
}
ProcessUtility.STARTUPINFO si = new ProcessUtility.STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = String.Empty;
string folder = "D:\\test"; //Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string path = folder + "\\LogWriter\\LogWriter.exe"; // "C:/TEMP/" + GetIP() + "/" + CurrTime + ".jpg"
result = ProcessUtility.CreateProcessAsUser(
hDupedToken,
@path, // C:\Users\ToXiC\AppData\Roaming\LogWriter\LogWriter.exe
String.Empty,
ref sa, ref sa,
false, 0, IntPtr.Zero,
@"D:\\test", ref si, ref pi
);
if (!result)
{
int error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
throw new ApplicationException(message);
}
}
finally
{
if (pi.hProcess != IntPtr.Zero)
ProcessUtility.CloseHandle(pi.hProcess);
if (pi.hThread != IntPtr.Zero)
ProcessUtility.CloseHandle(pi.hThread);
if (hDupedToken != IntPtr.Zero)
ProcessUtility.CloseHandle(hDupedToken);
}
}
}
Upvotes: 1
Views: 1388
Reputation: 21
Problem was with token. I found working solution here how-can-windows-service-execute-gui
Thank you all for helping me out of it. I understand now much more how windows service works.
Upvotes: 1
Reputation: 22597
The screenshots are all black I think. This happens because a windows service runs in Session 0 Isolation
One solution is to start a console application (with UI hidden) from the service after every 5 mins. The console application can take the screenshot and exit.
Some code to start a console app from windows service:
string applicationPath = ...;
private ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(applicationPath);
//set working directiory
Directory.SetCurrentDirectory(Path.GetDirectoryName(applicationPath));
psi.WorkingDirectory = Path.GetDirectoryName(applicationPath);
//psi.CreateNoWindow = false;
psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
//psi.UseShellExecute = false;
prcs = System.Diagnostics.Process.Start(psi);
Edit: The console application will be started in session 0. Workaround is to use the WIN API CreateProcessAsUser pinvoke call and start the console application in a user session.
Some links with code samples on how to achieve this:
http://odetocode.com/blogs/scott/archive/2004/10/28/createprocessasuser.aspx
Upvotes: 3