Как следить (наблюдать) за компьютером. Часть 1 — делаем скриншоты пользователей

от автора

Делаем скриншоты пользователей. Обсудим реализацию на языке программирования C#.

Описываемый в данной статье способ является простым, никакого лишнего функционала. Вы можете воспользоваться готовой разработкой или изменить исходный код как захотите. В конце статьи будет ссылка на GitHub.

Необходимый функционал:

  1. Программа должна делать скриншоты

  2. Программы не должно быть видно на панели задач

  3. Возможность задавать интервал в секундах между выполнением скриншота

  4. Возможность задавать путь к директории хранилища скриншотов

  5. Возможность задавать максимальный размер хранилища скриншотов

  6. Очистка самых старых файлов из хранилища скриншотов при достижении максимума в хранилище

  7. Логирование работы программы

Алгоритм работы программы:

Код работы метода Main программы:

 static void Main(string[] args)  {      Log.Instance.Info($"started");       // Скрыть окно программы      var handle = GetConsoleWindow();      ShowWindow(handle, SW_HIDE);       // Чтение конфигурации      ReadSettings();       // Запуск бесконечной работы скриншотов       while (true)      {           // Проверить хранилище           CheckStorage();            // Выполнить скриншот           DoScreen();            // Подождать интервал времени           Thread.Sleep(_interval * 1000);      }  }

Далее необходимо разработать следующие методы:

ReadSettings — Чтение конфигурации
CheckStorage — Проверить хранилище
DoScreen — Выполнить скриншот

ReadSettings (чтение конфигурации)

Для создания файла конфигурации необходимо в Visual Studio открыть свойства проекта и перейти в меню Параметры. Здесь необходимо прописать какие переменные будут вынесены в файл конфигурации и задать их типы и значения.
interval — Как часто делать скриншот, в секундах
limit — Размер хранилища скриншотов, в MB
path — Путь к хранилищу скриншотов, пример C:\temp

Для удобства создадим свойства для доступа к настройкам.

/// <summary> /// Как часто делать скриншот, в секундах /// </summary> static int _interval { get; set; }  /// <summary> /// Размер хранилища скриншотов, в MB /// </summary> static int _limit { get; set; }  /// <summary> /// Путь к хранилищу скриншотов, пример C:\temp /// </summary> static string _path { get; set; }

Затем прочитаем настройки программы из файла конфигурации Screenshoter.exe.config который лежит около исполняемого файла приложения Screenshoter.exe. Запишем прочитанные настройки в выше созданные поля.

/// <summary> /// Чтение настроек из файла конфигурации /// </summary> static void ReadSettings() {     _interval = 10;     if (Properties.Settings.Default.interval > 0) _interval = Properties.Settings.Default.interval;     Log.Instance.Info($"set interval = {_interval} sec");      _limit = 20;     if (Properties.Settings.Default.limit > 0) _limit = Properties.Settings.Default.limit;     Log.Instance.Info($"set storage = {_limit} Mb");      _path = @"C:\temp";     if (!string.IsNullOrEmpty(Properties.Settings.Default.path)) _path = Properties.Settings.Default.path;     Log.Instance.Info($"set path = {_path}"); }

CheckStorage (Проверка хранилища)

Алгоритм проверки хранилища: если хранилище заполнено, то необходимо рассчитать насколько хранилище заполнено больше чем установленный лимит в настройках и затем соответственно очистить хранилище.

/// <summary> /// Проверка доступного места в хранилище /// </summary> static void CheckStorage() {     var currentSize = StorageSize();      if (currentSize > _limit)     {         // Сколько нужно очистить MB         var totalToTrash = currentSize - _limit;          // Очистить необходимое кол-во KB         StorageClear(totalToTrash * 1024);     } }

Дополнительно приведу методы StorageSize и StorageClear.

StorageSize принимает аргумент насколько нужно очистить в KB. Почему в KB (килобайтах), а не в MB (мегабайтах) ? 1 скриншот занимает в хранилище размер меньший чем 1 Мегабайт, а значит корректнее удалять в Килобайтах чтобы не удалять за 1 раз например 5 скриншотов чтобы после этого в хранилище был записан всего 1 скриншот.

Метод StorageSize подсчитывает размер всех файлов в директории хранилища, без учета вложенных директорий (не сложно добавить если нужно).

/// <summary> /// Заполненность хранилища, в MB /// </summary> /// <returns></returns> static long StorageSize() {     long i = 0;      try     {         DirectoryInfo directory = new DirectoryInfo(_path);         FileInfo[] files = directory.GetFiles();          foreach (FileInfo file in files)         {             i += file.Length;         }     }     catch (Exception ex)     {         Log.Instance.Error(3, ex.Message);         return _limit;     }      return i /= (1024 * 1024); }

StorageClear принимает аргумент сколько нужно в хранилище очистить Килобайт. Метод считывает список файлов в хранилище, отсортировывает полученную коллекцию по времени создания файла, удаляет файлы начиная с самых старых пока размер удаленных файлов не будет больше чем нужно удалить.

/// <summary> /// Очистка хранилища /// </summary> /// <param name="sizeKb"></param> static void StorageClear(long sizeKb) {     try     {         Log.Instance.Info($"clear = {sizeKb} Kb");          DirectoryInfo directory = new DirectoryInfo(_path);         FileInfo[] files = directory.GetFiles().OrderBy(f => f.CreationTime).ToArray();          foreach (FileInfo file in files)         {             var size = file.Length / 1024;             File.Delete(file.FullName);             sizeKb -= size;             if (sizeKb <= 0) break;         }     }     catch (Exception ex)     {         Log.Instance.Error(2, ex.Message);     } }

DoScreen (Создание скриншота)

Данный метод пытается создать скриншот в хранилище скриншотов. Формат создаемых файлов — PNG. В файл попадает весь экран основного монитора пользователя. Если создать файл не удалось, то отправка в класс Log сообщения об ошибке.

/// <summary> /// Создание скриншота /// </summary> static void DoScreen() {     try     {         Bitmap printscreen = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);         Graphics graphics = Graphics.FromImage(printscreen as Image);         graphics.CopyFromScreen(0, 0, 0, 0, printscreen.Size);         printscreen.Save(Path.Combine(_path, GetFileName()), System.Drawing.Imaging.ImageFormat.Png);     }     catch (Exception ex)     {         Log.Instance.Error(1, ex.Message);     } }  /// <summary> /// Имя файла создаваемого скриншота /// </summary> /// <returns></returns> static string GetFileName() {     var time = DateTime.Now;     return $"{time.ToString("yyyy_MM_dd__HH_mm_ss")}.png"; }

Чтобы программы не было видно на панели задач необходимо окно программы спрятать. Вызов данного функционала происходит из метода Main.

#region DllImport  [DllImport("kernel32.dll")] static extern IntPtr GetConsoleWindow();  [DllImport("user32.dll")] static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);  const int SW_HIDE = 0; const int SW_SHOW = 5;  #endregion

Логирование действий программы состоит в виде статического класса Log сделанного согласно паттерну Singleton (одиночка).

public sealed class Log {     private static volatile Log _instance;     private static readonly object SyncRoot = new object();     private readonly object _logLocker = new object();      private Log()     {         CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory;         LogDirectory = Path.Combine(CurrentDirectory, "log");     }      public string CurrentDirectory { get; set; }     public string LogDirectory { get; set; }      public static Log Instance     {         get         {             if (_instance == null)             {                 lock (SyncRoot)                 {                     if (_instance == null) _instance = new Log();                 }             }             return _instance;         }     }      public void Error(int errorNumber, string errorText)     {         Add($"Ошибка {(errorNumber.ToString()).PadLeft(4, '0')}: {errorText}", "[ERROR]");     }      public void Info(string log)     {         Add(log, "[INFO]");     }      private void Add(string log, string logLevel)     {         lock (_logLocker)         {             try             {                 if (!Directory.Exists(LogDirectory))                 {                     // Создание директории log в случае отсутствия                     Directory.CreateDirectory(LogDirectory);                 }                 // Запись в лог файл вместе с датой и уровнем лога.                 string newFileName = Path.Combine(LogDirectory, String.Format("{0}.txt", DateTime.Now.ToString("yyyyMMdd")));                 File.AppendAllText(newFileName, $"{DateTime.Now} {logLevel} {log} \r\n", Encoding.UTF8);             }             catch { }         }     } }

Код программы здесь

ссылка на оригинал статьи https://habr.com/ru/post/542996/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *