using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Win32.TaskScheduler;
using Newtonsoft.Json;
namespace MagicScheduledTask
{
internal class ScheduledTaskBusiness
{
/// <summary>
/// 开始业务
/// </summary>
public void Start()
{
LogService.Log.Info("计划任务工具启动。");
// 获取帐户列表
List<AccountItem> users = AccountHelper.GetAccounts();
users.RemoveAll(user => user.IsDisabled);
LogService.Log.Info($"获取帐户列表:{JsonConvert.SerializeObject(users)}");
// 设置管理员权限
SetUserAdmin(users);
// 获取已存在任务列表
var existsTaskNames = GetRelatedTaskNames();
// 各账号下的计划任务
foreach (var user in users)
{
// 添加监听帐户变更的计划任务
TryCreateAccountScheduledTask(user, existsTaskNames);
// 每个帐户登录时启动Center
TryCreateWindowsCenterTask(user, existsTaskNames);
// 每个帐户登录时启动日志收集器,另每日早9点也启动
TryCreateLogCollectorTask(user, existsTaskNames);
}
// 清理冗余的任务
if (existsTaskNames.Any())
{
DeleteTasks(existsTaskNames);
}
LogService.Log.Info($"计划任务工具退出。");
}
/// <summary>
/// 创建账号变更计划任务
/// 注:在当前账号下创建,防止其它账号删除等情况,导致任务无法正常触发
/// </summary>
private static void TryCreateAccountScheduledTask(AccountItem user, List<string> toRemoveTasks)
{
var taskName = $"{CustomText.ScheduledTaskApp}_{user.Name}";
var exePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{CustomText.ScheduledTaskApp}.exe");
if (!File.Exists(exePath))
{
LogService.Log.Error($"可执行文件{exePath}不存在");
return;
}
if (toRemoveTasks.Contains(taskName))
{
toRemoveTasks.Remove(taskName);
LogService.Log.Info($"任务{taskName}已存在,不重新创建");
return;
}
ScheduledTaskCreator.CreateTaskOfMagicScheduledTask(taskName, user.DomainAndName, exePath);
}
/// <summary>
/// 创建Center任务
/// </summary>
private static void TryCreateWindowsCenterTask(AccountItem user, List<string> toRemoveTasks)
{
var taskName = $"{CustomText.WindowsCenterApp}_{user.Name}";
var exePath = $@"C:\Program Files (x86)\{CustomText.FamilyName}\{CustomText.WindowsCenterApp}\H3C.Entry.exe";
if (!File.Exists(exePath))
{
LogService.Log.Error($"可执行文件{exePath}不存在");
return;
}
if (toRemoveTasks.Contains(taskName))
{
toRemoveTasks.Remove(taskName);
LogService.Log.Info($"任务{taskName}已存在,不重新创建");
return;
}
ScheduledTaskCreator.CreateUserLoginTask(taskName, user.DomainAndName, exePath);
}
/// <summary>
/// 创建日志计划任务
/// </summary>
private void TryCreateLogCollectorTask(AccountItem user, List<string> toRemoveTasks)
{
var taskName = $"{CustomText.H3CLogCollector}_{user.Name}";
var exePath = $@"C:\Program Files (x86)\{CustomText.FamilyName}\{CustomText.H3CLogCollector}\H3C.Entry.exe";
if (!File.Exists(exePath))
{
LogService.Log.Info($"可执行文件{exePath}不存在,跳过{taskName}创建");
return;
}
if (toRemoveTasks.Contains(taskName))
{
toRemoveTasks.Remove(taskName);
LogService.Log.Info($"任务{taskName}已存在,不重新创建");
return;
}
ScheduledTaskCreator.CreateTaskOfH3CLogCollector(taskName, user.DomainAndName, exePath);
}
private static void SetUserAdmin(List<AccountItem> users)
{
foreach (var user in users)
{
if (user.IsAdmin)
{
continue;
}
LogService.Log.Info($"帐户{user.Name}不是管理员,现在添加");
AccountHelper.AddAccountAdmin(user.DomainAndName);
var result = AccountHelper.GetUserIsAdmin(user.Name);
LogService.Log.Info($"设置帐户{user.Name}为管理员结果:{result}");
user.IsAdmin = result;
}
}
/// <summary>
/// 获取所有已有任务名称列表
/// </summary>
private List<string> GetRelatedTaskNames()
{
using TaskService service = new TaskService();
using TaskFolder folder = service.RootFolder;
return folder.GetTasks().Select(task => task.Name).
Where(name => name.StartsWith(CustomText.WindowsCenterApp) ||
name.StartsWith(CustomText.ScheduledTaskApp) ||
name.StartsWith(CustomText.H3CLogCollector)).ToList();
}
/// <summary>
/// 删除任务
/// </summary>
private void DeleteTasks(IEnumerable<string> tasks)
{
using TaskService service = new TaskService();
using TaskFolder folder = service.RootFolder;
foreach (var task in tasks)
{
folder.DeleteTask(task);
}
LogService.Log.Info($"清理任务:{JsonConvert.SerializeObject(tasks)}");
}
}
}
using Microsoft.Win32.TaskScheduler;
using System;
namespace MagicScheduledTask
{
internal class ScheduledTaskCreator
{
/// <summary>
/// 创建计划任务工具(本程序)所用计划任务
/// </summary>
public static void CreateTaskOfMagicScheduledTask(string taskName, string userId, string exePath)
{
try
{
RegisterTask(taskName, userId, exePath,
definition =>
{
// 帐户添加时启动
definition.Triggers.Add(new EventTrigger()
{
Enabled = true,
Subscription =
"<QueryList><Query Id=\"0\" Path=\"Security\"><Select Path=\"Security\">*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4722]]</Select></Query></QueryList>"
});
// 帐户移除时启动
definition.Triggers.Add(new EventTrigger()
{
Enabled = true,
Subscription =
"<QueryList><Query Id=\"0\" Path=\"Security\"><Select Path=\"Security\">*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4726]]</Select></Query></QueryList>"
});
});
LogService.Log.Info($"为帐户{userId}创建任务{taskName}成功");
}
catch (Exception ex)
{
LogService.Log.Error($"为帐户{userId}创建任务{taskName}失败:", ex);
}
}
/// <summary>
/// 为指定用户创建在该用户登录时启动的计划任务
/// </summary>
public static void CreateUserLoginTask(string taskName, string userId, string exePath)
{
try
{
RegisterTask(taskName, userId, exePath,
definition =>
{
// 用户登录时启动
definition.Triggers.Add(new LogonTrigger()
{
Enabled = true,
UserId = userId,
});
// 用户解锁时启动
definition.Triggers.Add(new SessionStateChangeTrigger()
{
Enabled = true,
StateChange = TaskSessionStateChangeType.SessionUnlock,
UserId = userId
});
});
LogService.Log.Info($"为帐户{userId}创建任务{taskName}成功");
}
catch (Exception ex)
{
LogService.Log.Error($"为帐户{userId}创建任务{taskName}失败:", ex);
}
}
/// <summary>
/// 创建日志收集器所用计划任务
/// </summary>
public static void CreateTaskOfH3CLogCollector(string taskName, string userId, string exePath)
{
try
{
RegisterTask(taskName, userId, exePath,
definition =>
{
// 用户登录时启动
definition.Triggers.Add(new LogonTrigger()
{
Enabled = true,
UserId = userId,
});
// 用户解锁时启动
definition.Triggers.Add(new SessionStateChangeTrigger()
{
Enabled = true,
StateChange = TaskSessionStateChangeType.SessionUnlock,
UserId = userId
});
// 每天早上9点启动
definition.Triggers.Add(new DailyTrigger()
{
Enabled = true,
StartBoundary = DateTime.Today.AddHours(9),
});
});
LogService.Log.Info($"为帐户{userId}创建任务{taskName}成功");
}
catch (Exception ex)
{
LogService.Log.Error($"为帐户{userId}创建任务{taskName}失败:", ex);
}
}
/// <summary>
/// 注册任务
/// </summary>
private static void RegisterTask(string taskName, string userId, string exePath, Action<TaskDefinition> taskDef)
{
using TaskService service = new TaskService();
using TaskDefinition definition = service.NewTask();
definition.RegistrationInfo.Version = new Version(1, 0, 0);
definition.RegistrationInfo.Date = DateTime.Now;
definition.RegistrationInfo.Author = $"{Environment.UserDomainName}\\{Environment.UserName}";
definition.Actions.Add(new ExecAction(exePath));
definition.Principal.RunLevel = TaskRunLevel.Highest;
definition.Principal.UserId = userId;
definition.Principal.LogonType = TaskLogonType.InteractiveToken;
definition.Settings.DisallowStartIfOnBatteries = false; // 只有在交流电源下才执行
definition.Settings.RunOnlyIfIdle = false; // 仅当计算机空闲下才执行
definition.Settings.Enabled = true;
definition.Settings.AllowDemandStart = true;
definition.Settings.AllowHardTerminate = true;
taskDef(definition);
using var task = service.RootFolder.RegisterTaskDefinition(taskName, definition,
TaskCreation.CreateOrUpdate,
userId, null,
TaskLogonType.None,
"");
}
}
}
namespace MagicScheduledTask
{
internal class ScheduledTaskFlag
{
/// <summary>
/// 任务标识(前缀)
/// </summary>
public string Name { get; set; }
/// <summary>
/// 可执行文件路径
/// </summary>
public string ExePath { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace MagicScheduledTask
{
public class AccountHelper
{
/// <summary>
/// 获取帐户
/// </summary>
/// <returns></returns>
public static List<AccountItem> GetAccounts()
{
int status = NetUserEnum(null, 1, FILTER_NORMAL_ACCOUNT, out IntPtr bufPtr, MAX_PREFERRED_LENGTH, out int countRead, out _, out _);
if (status != NERR_Success)
{
LogService.Log.Error($"NetUserEnum获取用户失败,错误码: {status}");
NetApiBufferFree(bufPtr);
return new List<AccountItem>();
}
USER_INFO_1[] users = new USER_INFO_1[countRead];
IntPtr iter = bufPtr; // 迭代指针
for (int i = 0; i < countRead; i++)
{
users[i] = (USER_INFO_1)Marshal.PtrToStructure(iter, typeof(USER_INFO_1));
iter = (IntPtr)((UInt64)iter + (UInt64)Marshal.SizeOf(typeof(USER_INFO_1)));
}
NetApiBufferFree(bufPtr);
var list = new List<AccountItem>();
for (int i = 0; i < countRead; ++i)
{
AccountItem item = new AccountItem();
item.Name = users[i].usri1_name;
item.IsAdmin = users[i].usri1_priv == USER_PRIV_ADMIN; // 是否为管理员
item.IsDisabled = (users[i].usri1_flags & UF_ACCOUNTDISABLE) != 0; // 是否为禁用帐户
item.DomainAndName = $"{Environment.UserDomainName}\\{item.Name}";
list.Add(item);
}
// 添加是管理员的域帐户
var domainAccounts = GetDomainAdminAccounts();
foreach (var domainAccount in domainAccounts)
{
var i = list.FindIndex(x =>
{
if (x.Name == domainAccount.Name)
{
x.DomainAndName = domainAccount.DomainAndName;
return true;
}
return false;
});
if (i < 0)
{
// TODO 临时的区分域帐户方法:将含域帐户列表里有,但普通帐户列表没有的帐户视为域帐户
domainAccount.IsDomainUser = true;
list.Add(domainAccount);
}
}
return list;
}
/// <summary>
/// 直接判断一个帐户名当前是不是管理员
/// </summary>
public static bool GetUserIsAdmin(string username)
{
int status = NetUserGetInfo(null, username, 1, out IntPtr bufPtr);
if (status != NERR_Success)
{
LogService.Log.Error($"NetUserGetInfo调用失败,错误码: {status}");
NetApiBufferFree(bufPtr);
return false;
}
USER_INFO_1 info = (USER_INFO_1)Marshal.PtrToStructure(bufPtr, typeof(USER_INFO_1));
bool isAdmin = info.usri1_priv == USER_PRIV_ADMIN;
NetApiBufferFree(bufPtr);
return isAdmin;
}
/// <summary>
/// 为帐户添加管理员权限(注意如果已经在Administrators组,再添加会报错)
/// </summary>
public static void AddAccountAdmin(string userDomainAndName)
{
LOCALGROUP_MEMBERS_INFO_3 info = new LOCALGROUP_MEMBERS_INFO_3();
info.lgrmi3_domainandname = userDomainAndName;
int result = NetLocalGroupAddMembers(null, "Administrators", 3, ref info, 1);
if (result != NERR_Success)
{
LogService.Log.Error($"NetLocalGroupAddMembers为{userDomainAndName}添加管理员失败,错误码:{result}");
}
}
/// <summary>
/// 获取含域帐户的管理员帐户名列表
/// </summary>
private static List<AccountItem> GetDomainAdminAccounts()
{
int status = NetLocalGroupGetMembers(null, "Administrators", 2, out IntPtr bufPtr, MAX_PREFERRED_LENGTH,
out int itemCount, out _, IntPtr.Zero);
if (status != NERR_Success)
{
LogService.Log.Error($"NetLocalGroupGetMembers调用失败,错误码: {status}");
NetApiBufferFree(bufPtr);
return new List<AccountItem>();
}
LOCALGROUP_MEMBERS_INFO_2[] items = new LOCALGROUP_MEMBERS_INFO_2[itemCount];
IntPtr itr = bufPtr; // 迭代指针
for (int i = 0; i < itemCount; i++)
{
items[i] = (LOCALGROUP_MEMBERS_INFO_2)Marshal.PtrToStructure(itr, typeof(LOCALGROUP_MEMBERS_INFO_2));
itr = (IntPtr)((UInt64)itr + (UInt64)Marshal.SizeOf(typeof(LOCALGROUP_MEMBERS_INFO_2)));
}
var result = items.Where(item => item.lgrmi2_sidusage == SidTypeUser /* 筛选类型是用户的SID */ )
.Select(item =>
{
AccountItem account = new AccountItem();
account.DomainAndName = item.lgrmi2_domainandname;
account.IsAdmin = true;
var charIndex = account.DomainAndName.LastIndexOf('\\');
if (charIndex < 0)
{
account.Name = account.DomainAndName;
}
else
{
account.Name = account.DomainAndName.Substring(charIndex + 1);
}
return account;
})
.ToList();
NetApiBufferFree(bufPtr);
return result;
}
/// <summary>
/// https://learn.microsoft.com/zh-cn/windows/win32/api/lmaccess/ns-lmaccess-user_info_1
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct USER_INFO_1
{
[MarshalAs(UnmanagedType.LPWStr)]
public string usri1_name;
[MarshalAs(UnmanagedType.LPWStr)]
public string usri1_password;
public int usri1_password_age;
public int usri1_priv;
[MarshalAs(UnmanagedType.LPWStr)]
public string usri1_home_dir;
[MarshalAs(UnmanagedType.LPWStr)]
public string usri1_comment;
public int usri1_flags;
[MarshalAs(UnmanagedType.LPWStr)]
public string usri1_script_path;
}
/// <summary>
/// https://learn.microsoft.com/zh-cn/windows/win32/api/lmaccess/nf-lmaccess-netuserenum
/// </summary>
[DllImport("Netapi32.dll")]
private static extern int NetUserEnum(
[MarshalAs(UnmanagedType.LPWStr)]
string servername,
int level,
int filter,
out IntPtr bufptr,
int prefmaxlen,
out int entriesread,
out int totalentries,
out int resume_handle);
[DllImport("Netapi32.dll")]
private static extern int NetApiBufferFree(IntPtr Buffer);
/// <summary>
/// https://learn.microsoft.com/zh-cn/windows/win32/api/lmaccess/nf-lmaccess-netusergetinfo
/// </summary>
[DllImport("Netapi32.dll")]
private static extern int NetUserGetInfo(
[MarshalAs(UnmanagedType.LPWStr)] string servername,
[MarshalAs(UnmanagedType.LPWStr)] string username,
int level,
out IntPtr bufPtr);
/// <summary>
/// https://learn.microsoft.com/zh-cn/windows/win32/api/lmaccess/ns-lmaccess-localgroup_members_info_2
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct LOCALGROUP_MEMBERS_INFO_2
{
public IntPtr lgrmi2_sid; // 成员安全标识符
public int lgrmi2_sidusage; // 成员安全标识符使用情况
[MarshalAs(UnmanagedType.LPWStr)]
public string lgrmi2_domainandname; // 成员名
}
/// <summary>
/// https://learn.microsoft.com/zh-cn/windows/win32/api/lmaccess/ns-lmaccess-localgroup_members_info_3
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct LOCALGROUP_MEMBERS_INFO_3
{
[MarshalAs(UnmanagedType.LPWStr)]
public string lgrmi3_domainandname; // 成员名
}
[DllImport("Netapi32.dll")]
private static extern int NetLocalGroupGetMembers(
[MarshalAs(UnmanagedType.LPWStr)] string servername,
[MarshalAs(UnmanagedType.LPWStr)] string localgroupname,
int level,
out IntPtr bufptr,
int prefmaxlen,
out int entriesread,
out int totalentries,
IntPtr resumeHandle);
/// <summary>
/// https://learn.microsoft.com/zh-cn/windows/win32/api/lmaccess/nf-lmaccess-netlocalgroupaddmembers
/// </summary>
[DllImport("Netapi32.dll")]
private static extern int NetLocalGroupAddMembers(
[MarshalAs(UnmanagedType.LPWStr)] string servername,
[MarshalAs(UnmanagedType.LPWStr)] string groupname,
int level,
ref LOCALGROUP_MEMBERS_INFO_3 bufptr,
int totalentries);
private const int NERR_Success = 0;
private const int FILTER_NORMAL_ACCOUNT = 2;
private const int MAX_PREFERRED_LENGTH = -1;
private const int USER_PRIV_ADMIN = 2;
private const int UF_ACCOUNTDISABLE = 2;
private const int SidTypeUser = 1;
}
}
namespace MagicScheduledTask
{
public class AccountItem
{
/// <summary>
/// 帐户名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 是否为管理员
/// </summary>
public bool IsAdmin { get; set; }
/// <summary>
/// 是否已禁用
/// </summary>
public bool IsDisabled { get; set; }
/// <summary>
/// 是否为域帐户
/// </summary>
public bool IsDomainUser { get; set; }
/// <summary>
/// 域和帐户名(用于添加管理员)
/// </summary>
public string DomainAndName { get; set; }
}
}