系列文章
C#项目–业务单据号生成器(定义规则、自动编号、流水号)
本文链接:https://blog.csdn.net/youcheng_ge/article/details/129129787
C#项目–开始日期结束日期范围计算(上周、本周、明年、前年等)
本文链接:https://blog.csdn.net/youcheng_ge/article/details/129040663
C#项目–数据实体类使用
本文链接:https://blog.csdn.net/youcheng_ge/article/details/128816638
C#项目–单据审批流方案
本文链接:https://blog.csdn.net/youcheng_ge/article/details/128972545
C#项目–二维码标签制作及打印(完整版)
本文链接:https://blog.csdn.net/youcheng_ge/article/details/126884228
C#项目–条码管理操作手册
本文链接:https://blog.csdn.net/youcheng_ge/article/details/126589496
C#项目–WebAPI发布和IIS部署,及异常处理
本文链接:https://blog.csdn.net/youcheng_ge/article/details/126539836
C#项目–提高编程效率,代码自动生成
本文链接:https://blog.csdn.net/youcheng_ge/article/details/126890673
C#项目–提高编程效率,Excel数据导入工具
本文链接:https://blog.csdn.net/youcheng_ge/article/details/126427323
C#项目–Windows服务(Service)安装及启停方案
本文链接:https://blog.csdn.net/youcheng_ge/article/details/124053794
C#项目–穿透Session隔离,服务调用外部程序(无窗体界面解决)
本文链接:https://blog.csdn.net/youcheng_ge/article/details/124053033
C#项目–任务计划实现,使用Quartz类
本文链接:https://blog.csdn.net/youcheng_ge/article/details/123667723
C#项目–《周计划管理关于产前准备模块》解决方案20200203
本文链接:https://blog.csdn.net/youcheng_ge/article/details/122919543
C#项目–项目中,源码解析的正则表达式
本文链接:https://blog.csdn.net/youcheng_ge/article/details/118337074
C#项目–如何获取文件版本和MD5值
本文链接:https://blog.csdn.net/youcheng_ge/article/details/112513871
C#项目–如何测试网络是否连通
本文链接:https://blog.csdn.net/youcheng_ge/article/details/110137288
目录
前言
我能抽象出整个世界,但是我不能抽象你。 想让你成为私有常量,这样外部函数就无法访问你。 又想让你成为全局常量,这样在我的整个生命周期都可以调用你。 可惜世上没有这样的常量,我也无法定义你,因为你在我心中是那么的具体。
哈喽大家好,本专栏为【项目实战】专栏,区别于【底层库】专栏,本专栏主要介绍项目开发过程中遇到的各种问题,以及解决方案,甚至是我项目开发过程,我觉得可以使用工具实现编程的方案。
本专栏会持续更新,不断完善【项目实战】,大家有任何问题,可以私信我。本专栏之间关联性较弱(文章和文章之间依赖性较弱,没有阅读顺序可以随意阅读),如果您对本专栏感兴趣,欢迎关注,我将带你用最简洁的代码,实现最复杂的功能。
一、面临问题
1.1 需求描述
公司行政小姐姐,提了个需求要求程序开机自运行,无需人工干预。首先获得该需求,两种途径实现:①将程序放入开机启动项目录
②将程序写入服务,通过服务service控制。
最终综合考虑员工电脑权限、安全性,我拍案决定采用第二种方式,如下图所示:
于是产生了新的问题,AutoPlayerService为服务程序,它自动调用外部可执行程序(.exe),调用异常无UI界面。
打个比方说,我在每天8:00自动播放企宣视频。现在视频操作界面打不开了,只能够听见声音。【任务管理器】中可以看到该进程,正在运行,就是不显示Windows窗体界面,无法对播放器进行干预,也显示不了画面。
1.2 问题原因
在Windows XP, Windows Server 2003或者更早期的Windows操作系统中,所有的服务和应用程序都是运行在与第一个登录到控制台的用户得Session中。这个Session叫做Session 0。在Session 0 中一起运行服务和用户应用程序,由于服务是以高权限运行的,所以会造成一些安全风险。
在Windows7之后版本,所有的服务在session 0中运行, 应有程序在session1、session2....中运行,他们存在隔离,所以资源不能共享,在session 0不能够现实Windows Form界面。
二、解决方案
所以我们要解决这个问题,要 “穿透Session隔离”,具体术语的解释,可以查看微软官网,英文文档,可通过翻译软件学习一下。
三、软件开发(源码展示)
VS创建帮助类,InteropsHelper.cs,复制如下源码:
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace TaskLib
{
public class InteropsHelper
{
public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
public static void ShowMessageBox(string message, string title)
{
int resp = 0;
WTSSendMessage(
WTS_CURRENT_SERVER_HANDLE,
WTSGetActiveConsoleSessionId(),
title, title.Length,
message, message.Length,
0, 0, out resp, false);
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int WTSGetActiveConsoleSessionId();
[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSSendMessage(
IntPtr hServer,
int SessionId,
String pTitle,
int TitleLength,
String pMessage,
int MessageLength,
int Style,
int Timeout,
out int pResponse,
bool bWait);
/// <summary>
/// 调用进程
/// </summary>
/// <param name="app">应用程序名(包含扩展名)</param>
/// <param name="path">应用程序所在目录</param>
/// <param name="a_strParam">传入参数(第一个参数有空格)</param>
public static void CreateProcess(string app, string path,string a_strParam)
{
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
IntPtr hDupedToken = IntPtr.Zero;
try
{
bool result;
IntPtr hToken = WindowsIdentity.GetCurrent().Token;
sa.Length = Marshal.SizeOf(sa);
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
int dwSessionID = WTSGetActiveConsoleSessionId();
result = WTSQueryUserToken(dwSessionID, out hToken);
//if (!result)//此处有错误
//{
// ShowMessageBox("WTSQueryUserToken failed", "AlertService Message");
//}
result = DuplicateTokenEx(
hToken,
GENERIC_ALL_ACCESS,
ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary,
ref hDupedToken
);
//if (!result)
//{
// ShowMessageBox("DuplicateTokenEx failed", "AlertService Message");
//}
IntPtr lpEnvironment = IntPtr.Zero;
result = CreateEnvironmentBlock(out lpEnvironment, hDupedToken, false);
//if (!result)
//{
// ShowMessageBox("CreateEnvironmentBlock failed", "AlertService Message");
//}
result = CreateProcessAsUser(
hDupedToken,
app,
a_strParam,
ref sa, ref sa,
false, 0, IntPtr.Zero,
path, ref si, ref pi);
//if (!result)
//{
// int error = Marshal.GetLastWin32Error();
// string message = String.Format("CreateProcessAsUser Error: {0}", error);
// ShowMessageBox(message, "AlertService Message");
//}
}
catch (Exception)
{
throw;
}
finally
{
if (pi.hProcess != IntPtr.Zero)
CloseHandle(pi.hProcess);
if (pi.hThread != IntPtr.Zero)
CloseHandle(pi.hThread);
if (hDupedToken != IntPtr.Zero)
CloseHandle(hDupedToken);
}
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public Int32 dwProcessID;
public Int32 dwThreadID;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public Int32 Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public const int GENERIC_ALL_ACCESS = 0x10000000;
[DllImport("kernel32.dll", SetLastError = true,
CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", SetLastError = true,
CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern bool CreateProcessAsUser(
IntPtr hToken,//第一个hToken,是跟用户相关的访问令牌
string lpApplicationName,//你所创建进程文件的所在路径包含文件名(最好带着扩展名)windows api 把路径转换为短路径
string lpCommandLine,//放你给被创建进程传递的参数
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandle,
Int32 dwCreationFlags,
IntPtr lpEnvrionment,
string lpCurrentDirectory,//指的是进程运行的目录(注意不是进程文件所在的目录),为NULL时默认随父进程
ref STARTUPINFO lpStartupInfo,
ref PROCESS_INFORMATION lpProcessInformation);//这个是个出参。 返回被创建进程的信息,包括进程PID等
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool DuplicateTokenEx(
IntPtr hExistingToken,
Int32 dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel,
Int32 dwTokenType,
ref IntPtr phNewToken);
[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSQueryUserToken(
Int32 sessionId,
out IntPtr Token);
[DllImport("userenv.dll", SetLastError = true)]
static extern bool CreateEnvironmentBlock(
out IntPtr lpEnvironment,
IntPtr hToken,
bool bInherit);
}
}
四、调用方法
我已将该调用方法封装,你可以直接引入TaskLib.dll组件,只需一行代码搞定。是的,你没有看错就这么简单。编程是件简单的事情,不要重复作业,不要内耗,保护自己的好头发呀。
//参数1:程序名
//参数2:程序所在目录
//参数3:传入参数(参数以空格分隔,参数含空以引号包含)
InteropsHelper.CreateProcess(Const.ct_strPlayerName, l_strPath,
string.Format(Const.ct_strMediaCMD1, a_strFiles, "播放:" + a_strFiles));
五、运行效果
这是我做的项目,感兴趣的我们一起交流下吧。
六、资源链接
TaskLib.dll,组件下载,减少编码类,减少秃顶几率.。
其中该组件,包含xml、文件转移、日志记录、发送邮件、SQLite操作、Datatable和list互相转换等类,visual studio 下按F12有使用说明。
链接:https://pan.baidu.com/s/1o6exHhKJv_SmAszdOuCmyQ?pwd=4v66
提取码:4v66