本文首发于:码友网--一个专注.NET/.NET Core开发的编程爱好者社区。

文章目录

C#/.NET基于Topshelf创建Windows服务的系列文章目录:

  1. C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载 (1)
  2. 在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务) (2)
  3. C#/.NET基于Topshelf创建Windows服务的守护程序作为服务启动的客户端桌面程序不显示UI界面的问题分析和解决方案 (3)

前言

在上一篇文章《在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务)》的最后,我给大家抛出了一个遗留的问题--在将TopshelfDemoService程序作为Windows服务安装的情况下,由它守护并启动的客户端程序是没有UI界面的。到这里,我们得分析为什么会出现这个问题,为什么在桌面应用程序模式下可以显示UI界面,而在服务模式下没有UI界面?

分析问题(Session 0 隔离)

通过查阅资料,这是由于Session 0 隔离作用的结果。那么什么又是Session 0 隔离呢?

在Windows XP、Windows Server 2003 或早期Windows 系统时代,当第一个用户登录系统后服务和应用程序是在同一个Session 中运行的。这就是Session 0 如下图所示:

但是这种运行方式提高了系统安全风险,因为服务是通过提升了用户权限运行的,而应用程序往往是那些不具备管理员身份的普通用户运行的,其中的危险显而易见。

从Vista 开始Session 0 中只包含系统服务,其他应用程序则通过分离的Session 运行,将服务与应用程序隔离提高系统的安全性。如下图所示:

这样使得Session 0 与其他Session 之间无法进行交互,不能通过服务向桌面用户弹出信息窗口、UI 窗口等信息。这也就是为什么刚才我说那个图已经不能通过当前桌面进行截图了。

潜在的问题

解决方案

在了解了Session 0 隔离之后,给出一些有关创建服务程序以及由服务托管的驱动程序的建议:

1、与应用程序通信时,使用RPC、命名管道等C/S模式代替窗口消息

2、如果服务程序需要UI与用户交互的话,有两种方式:

①用WTSSendMessage来创建一个消息框与用户交互

②使用一个代理(agent)来完成跟用户的交互,服务程序通过CreateProcessAsUser创建代理。

并用RPC或者命名管道等方式跟代理通信,从而完成复杂的界面交互。

3、应该在用户的Session中查询显示属性,如果在Session 0中做这件事,将会得到不正确的结果。

4、明确地使用Local或者Global为命名对象命名,Local/为Session//BaseNamedObject/,Global/为BaseNamedObject/

5、将程序放在实际环境中测试是最好的方法,如果条件不允许,可以在XP的FUS下测试。在XP的FUS下能工作的服务程序将很可能可以在新版系统中工作,注意XP的FUS下的测试不能检测到在Session 0下跟视频驱动有关的问题

本文我们的服务程序将通过CreateProcessAsUser创建代理来实现Session 0隔离的穿透。

在项目[TopshelfDemoService]中创建一个静态扩展帮助类ProcessExtensions.cs,代码如下:

using System;
using System.Runtime.InteropServices; namespace TopshelfDemoService
{
/// <summary>
/// 进程静态扩展类
/// </summary>
public static class ProcessExtensions
{
#region Win32 Constants private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private const int CREATE_NO_WINDOW = 0x08000000; private const int CREATE_NEW_CONSOLE = 0x00000010; private const uint INVALID_SESSION_ID = 0xFFFFFFFF;
private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; #endregion #region DllImports [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
private static extern bool CreateProcessAsUser(
IntPtr hToken,
String lpApplicationName,
String lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandle,
uint dwCreationFlags,
IntPtr lpEnvironment,
String lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation); [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
private static extern bool DuplicateTokenEx(
IntPtr ExistingTokenHandle,
uint dwDesiredAccess,
IntPtr lpThreadAttributes,
int TokenType,
int ImpersonationLevel,
ref IntPtr DuplicateTokenHandle); [DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit); [DllImport("userenv.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); [DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hSnapshot); [DllImport("kernel32.dll")]
private static extern uint WTSGetActiveConsoleSessionId(); [DllImport("Wtsapi32.dll")]
private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken); [DllImport("wtsapi32.dll", SetLastError = true)]
private static extern int WTSEnumerateSessions(
IntPtr hServer,
int Reserved,
int Version,
ref IntPtr ppSessionInfo,
ref int pCount); #endregion #region Win32 Structs private enum SW
{
SW_HIDE = 0,
SW_SHOWNORMAL = 1,
SW_NORMAL = 1,
SW_SHOWMINIMIZED = 2,
SW_SHOWMAXIMIZED = 3,
SW_MAXIMIZE = 3,
SW_SHOWNOACTIVATE = 4,
SW_SHOW = 5,
SW_MINIMIZE = 6,
SW_SHOWMINNOACTIVE = 7,
SW_SHOWNA = 8,
SW_RESTORE = 9,
SW_SHOWDEFAULT = 10,
SW_MAX = 10
} private enum WTS_CONNECTSTATE_CLASS
{
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
} [StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
} private enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3,
} [StructLayout(LayoutKind.Sequential)]
private struct STARTUPINFO
{
public int cb;
public String lpReserved;
public String lpDesktop;
public String lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
} private enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation = 2
} [StructLayout(LayoutKind.Sequential)]
private struct WTS_SESSION_INFO
{
public readonly UInt32 SessionID; [MarshalAs(UnmanagedType.LPStr)]
public readonly String pWinStationName; public readonly WTS_CONNECTSTATE_CLASS State;
} #endregion // Gets the user token from the currently active session
private static bool GetSessionUserToken(ref IntPtr phUserToken)
{
var bResult = false;
var hImpersonationToken = IntPtr.Zero;
var activeSessionId = INVALID_SESSION_ID;
var pSessionInfo = IntPtr.Zero;
var sessionCount = 0; // Get a handle to the user access token for the current active session.
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
{
var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
var current = pSessionInfo; for (var i = 0; i < sessionCount; i++)
{
var si = (WTS_SESSION_INFO)Marshal.PtrToStructure(current, typeof(WTS_SESSION_INFO));
current += arrayElementSize; if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive)
{
activeSessionId = si.SessionID;
}
}
} // If enumerating did not work, fall back to the old method
if (activeSessionId == INVALID_SESSION_ID)
{
activeSessionId = WTSGetActiveConsoleSessionId();
} if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0)
{
// Convert the impersonation token to a primary token
bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary,
ref phUserToken); CloseHandle(hImpersonationToken);
} return bResult;
} public static bool StartProcessAsCurrentUser(string appPath, string cmdLine = null, string workDir = null, bool visible = true)
{
var hUserToken = IntPtr.Zero;
var startInfo = new STARTUPINFO();
var procInfo = new PROCESS_INFORMATION();
var pEnv = IntPtr.Zero;
int iResultOfCreateProcessAsUser; startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO)); try
{
if (!GetSessionUserToken(ref hUserToken))
{
throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed.");
} uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE);
startInfo.lpDesktop = "winsta0\\default"; if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false))
{
throw new Exception("StartProcessAsCurrentUser: CreateEnvironmentBlock failed.");
} if (!CreateProcessAsUser(hUserToken,
appPath, // Application Name
cmdLine, // Command Line
IntPtr.Zero,
IntPtr.Zero,
false,
dwCreationFlags,
pEnv,
workDir, // Working directory
ref startInfo,
out procInfo))
{
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed. Error Code -" + iResultOfCreateProcessAsUser);
} iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
}
finally
{
CloseHandle(hUserToken);
if (pEnv != IntPtr.Zero)
{
DestroyEnvironmentBlock(pEnv);
}
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
} return true;
} }
}

修改ProcessHelper.cs为如下代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; namespace TopshelfDemoService
{
/// <summary>
/// 进程处理帮助类
/// </summary>
internal class ProcessorHelper
{
/// <summary>
/// 获取当前计算机所有的进程列表(集合)
/// </summary>
/// <returns></returns>
public static List<Process> GetProcessList()
{
return GetProcesses().ToList();
} /// <summary>
/// 获取当前计算机所有的进程列表(数组)
/// </summary>
/// <returns></returns>
public static Process[] GetProcesses()
{
var processList = Process.GetProcesses();
return processList;
} /// <summary>
/// 判断指定的进程是否存在
/// </summary>
/// <param name="processName"></param>
/// <returns></returns>
public static bool IsProcessExists(string processName)
{
return Process.GetProcessesByName(processName).Length > 0;
} /// <summary>
/// 启动一个指定路径的应用程序
/// </summary>
/// <param name="applicationPath"></param>
/// <param name="args"></param>
public static void RunProcess(string applicationPath, string args = "")
{
try
{
ProcessExtensions.StartProcessAsCurrentUser(applicationPath, args);
}
catch (Exception e)
{
var psi = new ProcessStartInfo
{
FileName = applicationPath,
WindowStyle = ProcessWindowStyle.Normal,
Arguments = args
};
Process.Start(psi);
}
}
}
}

其中更改了方法RunProcess()的调用方式。

重新编译服务程序项目[TopshelfDemoService],并将它作为Windows服务安装,最后启动服务。守护进程服务将启动一个带UI界面的客户端程序。大功告成!!!

我是Rector,希望本文的关于Topshelf服务和守护程序设计对需要的朋友有所帮助。

感谢花你宝贵的时间阅读!!!

参考资料

穿透Session 0 隔离(一)

Windows中Session 0隔离对服务程序和驱动程序的影响

CreateProcessAsUser

源代码下载

本示例代码托管地址可以在原出处找到:示例代码下载地址

C#/.NET基于Topshelf创建Windows服务的守护程序作为服务启动的客户端桌面程序不显示UI界面的问题分析和解决方案的更多相关文章

  1. C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载(极速,简洁)

    本文首发于:码友网--一个专注.NET/.NET Core开发的编程爱好者社区. 文章目录 C#/.NET基于Topshelf创建Windows服务的系列文章目录: C#/.NET基于Topshelf ...

  2. 使用Topshelf创建Windows服务

    概述 Topshelf是创建Windows服务的另一种方法,老外的一篇文章Create a .NET Windows Service in 5 steps with Topshelf通过5个步骤详细的 ...

  3. Topshelf创建Windows服务

    使用Topshelf创建Windows服务 概述 Topshelf是创建Windows服务的另一种方法,老外的一篇文章Create a .NET Windows Service in 5 steps ...

  4. 【第三方插件】使用Topshelf创建Windows服务

    概述 Topshelf是创建Windows服务的另一种方法,老外的一篇文章Create a .NET Windows Service in 5 steps with Topshelf通过5个步骤详细的 ...

  5. 使用Topshelf创建Windows服务[转载]

    概述 Topshelf是创建Windows服务的另一种方法,老外的一篇文章Create a .NET Windows Service in 5 steps with Topshelf通过5个步骤详细的 ...

  6. [Solution] Microsoft Windows 服务(2) 使用Topshelf创建Windows服务

    除了通过.net提供的windows服务模板外,Topshelf是创建Windows服务的另一种方法. 官网教程:http://docs.topshelf-project.com/en/latest/ ...

  7. 使用Topshelf创建Windows 服务

    本文转载: http://www.cnblogs.com/aierong/archive/2012/05/28/2521409.html http://www.cnblogs.com/jys509/p ...

  8. 使用 Topshelf 创建 Windows 服务

    Ø  前言 C# 创建 Windows 服务的方式有很多种,Topshelf 就是其中一种方式,而且使用起来比较简单.下面使用 Visual Studio Ultimate 2013 演示一下具体的使 ...

  9. 微服务架构 - 搭建docker本地镜像仓库并提供权限校验及UI界面

    搭建docker本地镜像仓库并提供权限校验及UI界面 docker本地镜像仓库的作用跟maven私服差不多,特别是公司级或者是小组级开发好的docker仓库可以上传到本地镜像仓库中,需要用时,直接从本 ...

随机推荐

  1. _IO_FILE

    hctf2017的babyprintf解法是house of orange,深入学习了一下,牵扯出许多知识,这里先进行第一步:_IO_FILE结构 0x00 _IO_FILE glibc-2.2.1\ ...

  2. (1)JSTL的13个core标签库

     标准标签库JSTL的全名为:Java Server Pages Standard Tag Library. (jsp  standard tag library) JSTL主要提供了5大类标签库: ...

  3. 使用Spring AOP实现业务依赖解耦

    Spring IOC用于解决对象依赖之间的解耦,而Spring AOP则用于解决业务依赖之间的解耦: 统一在一个地方定义[通用功能],通过声明的方式定义这些通用的功能以何种[方式][织入]到某些[特定 ...

  4. 【Office_Word】Word排版

    文档排版的步骤: step1.先设置正文的样式 step2.再设置各级标题的样式 step3.最后在"多级列表"里设置各级标题编号 [注]最好按照这三步的顺序来排版,否则将会导致正 ...

  5. The Fourth Day

    迭代器 迭代器:迭代的工具 .什么是迭代:指的是一个重复的过程,每次重复称为一次迭代,并且每次重复的结果是下一次重复的初始值 例: while True: print('====>'') l=[ ...

  6. 【css】报错,错误代码77,CURLE_SSL_CACERT_BADFILE (77)解决方法

    CURLE_SSL_CACERT_BADFILE (77) - 读取 SSL CA 证书时遇到问题(可能是路径错误或访问权限问题) 在微信接口相关开发时容易出现此问题 这一般是因为服务更新了相关的软件 ...

  7. module_param

    该宏定义在include/linux/moduleparam.h中 #define ___module_cat(a,b) __mod_ ## a ## b #define __module_cat(a ...

  8. \include\configs\mx6q_sabresd.h

    /* * Copyright (C) 2012 Freescale Semiconductor, Inc. * * Configuration settings for the MX6Q Sabre ...

  9. Action的实现方式

    [Pojo方式] 1.概述 Pojo(Plain Ordinary Java Object)称为简单Java类,其实就是一个JavaBean. 2.示例 /** * Pojo类方式实现Action * ...

  10. python ratelimit使用

    1.https://pypi.org/project/ratelimit/