Implemented VirtualPrinterDriver project

This commit is contained in:
Marco Batzinger 2020-10-19 17:44:50 +02:00
parent f29c84821b
commit 5c87967c3f
125 changed files with 8191 additions and 0 deletions

View file

@ -0,0 +1,51 @@
namespace VirtualPrinter.Utils
{
public struct Files
{
public const string FILES = @"Files";
public const string PRINTER_SERVICE_EXE = "VPDAgent.exe";
public const string SETUP_DRIVER_EXE = "setupdrv.exe";
public const string AGENT_PROGRESS_EXE = "VPDAgentProgress.exe";
public const string DILIVERY_EXE = "delivery.exe";
public const string LICENCE_FILE = "";
public const string PRE_CONVERTER = @"C:\Program Files (x86)\MyPreConverter.exe ARG";
public const string POST_CONVERTER = @"C:\Program Files (x86)\MyPostConverter.exe ARG";
}
public struct Keys
{
public const string PRINTER_DRIVER_KEY32 = @"SOFTWARE\vpd\PrinterDriver";
public const string PRINTER_DRIVER_KEY64 = @"SOFTWARE\Wow6432Node\vpd\PrinterDriver";
public const string POSTCONVERTER_KEY = @"Application\Postconverter";
public const string PRECONVERTER_KEY = @"Application\Preconverter";
public const string CONVERTER_KEY = @"Converter";
public const string CONVERTER_PDF_KEY = CONVERTER_KEY + @"\PDF";
public const string CONVERTER_TIFF_KEY = CONVERTER_KEY + @"\TIFF";
public const string CONVERTER_REDIRECT_KEY = CONVERTER_KEY + @"\Redirect";
}
public struct KeyNames
{
public const string EXECUTABLE_FILE = "Executable File";
public const string INSTALLATION_DIR = "Installation Directory";
public const string THREADS = "Threads";
public const string SHOW_PROGRESS = "Show Progress";
public const string PAGES_PER_SHEET = "Pages per Sheet";
public const string FILE_NAME_MASK = "File name mask";
public const string OUTPUT_DIR = "Output Directory";
public const string ENABLED = "Enabled";
public const string MULTIPAGE = "Multipage";
public const string PRODUCE_PDFA = "Produce PDFA";
public const string ALLOW_PRINTING = "Allow printing";
public const string ALLOW_COPYING = "Allow printing";
public const string SUBSETTING = "Subsetting";
public const string QUALITY = "Image Quality";
public const string BITS_PIXEL = "Bits per pixel";
public const string COMPRESSION = "Compression";
public const string SERVER_PORT = "Server port";
public const string RENDER_DPI = "RENDER: DPI";
public const string FORMAT = "Intermediate Format";
public const string PRINT_FORMAT = "Format";
public const string PRINTER = "Printer";
}
}

View file

@ -0,0 +1,24 @@
using System.IO;
using VirtualPrinter.Agent.Core;
namespace VirtualPrinter.Utils
{
public class DirectoryHelper : IDirectoryHelper
{
public string GetOutputDirectory(IExConfig config)
{
if (string.IsNullOrWhiteSpace(config.ResolvedOutputDirectory))
{
var outputDir = Path.Combine(Path.GetTempPath(), "PrinterOutput");
if (!Directory.Exists(outputDir))
{
Directory.CreateDirectory(outputDir);
}
return outputDir;
}
return config.ResolvedOutputDirectory;
}
}
}

View file

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("VirtualPrinter.Utils")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("cd1c8e9d-5335-41ac-b0c0-88fd7c7c55f3")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,170 @@
using System;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.Win32;
using VirtualPrinter.Agent.Core;
using VirtualPrinter.Agent.Core.Enums;
namespace VirtualPrinter.Utils
{
public class RegistryRepository : IRegistryRepository
{
private const short DefaultServerPort = 9101;
public bool TryGetGhostscriptPath(out string path)
{
path = null;
try
{
var regView = GetRegistryView();
using(var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, regView))
{
const string gsSubKey = @"SOFTWARE\GPL Ghostscript";
using(var ghostscript = baseKey.OpenSubKey(gsSubKey))
{
CheckForNull(ghostscript, gsSubKey);
var subKeys = ghostscript.GetSubKeyNames();
var lastSubKey = subKeys.Last();
using(var subKey = ghostscript.OpenSubKey(lastSubKey))
{
CheckForNull(subKey, lastSubKey);
path = subKey.GetValue("GS_LIB").ToString().Split(';').FirstOrDefault();
if (string.IsNullOrWhiteSpace(path))
{
return false;
}
path = Directory.GetParent(path).FullName;
return true;
}
}
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
return false;
}
public IExConfig GetRegistryConfig()
{
var regView = GetRegistryView();
var subKey = GetSubKey();
var registryConfig = new RegistryConfig();
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, regView))
{
using (var driver = baseKey.OpenSubKey(subKey))
{
CheckForNull(driver, subKey);
using(var key = driver.OpenSubKey(Keys.POSTCONVERTER_KEY))
{
CheckForNull(key, Keys.POSTCONVERTER_KEY);
registryConfig.Postconverter = key.GetValue(KeyNames.EXECUTABLE_FILE).ToString();
}
using(var key = driver.OpenSubKey(Keys.PRECONVERTER_KEY))
{
CheckForNull(key, Keys.PRECONVERTER_KEY);
registryConfig.Preconverter = key.GetValue(KeyNames.EXECUTABLE_FILE).ToString();
}
using (var key = driver.OpenSubKey(Keys.CONVERTER_KEY))
{
CheckForNull(key, Keys.CONVERTER_KEY);
registryConfig.OutputDirectory = key.GetValue(KeyNames.OUTPUT_DIR).ToString();
registryConfig.FileNameMask = key.GetValue(KeyNames.FILE_NAME_MASK).ToString();
var portStr = key.GetValue(KeyNames.SERVER_PORT).ToString();
registryConfig.PrinterPort = short.TryParse(portStr, out var portVal) ? portVal : DefaultServerPort;
registryConfig.IntermediateFormat = key.GetValue(KeyNames.FORMAT)?.ToString().ToLower() == "ps" ? IntermediateFormat.Ps : IntermediateFormat.Xps;
}
}
}
return registryConfig;
}
[ContractAnnotation("key:null => void")]
private void CheckForNull(RegistryKey key, string keyName)
{
if (key == null)
{
throw new NullReferenceException(keyName);
}
}
public IUserConfig GetUserRegistryConfig(string sid)
{
var regView = GetRegistryView();
var userConfig = new UserRegistryConfig();
using (var users = RegistryKey.OpenBaseKey(RegistryHive.Users, regView))
{
var subKey = $@"{sid}\{Keys.PRINTER_DRIVER_KEY32}";
using (var driver = users.OpenSubKey(subKey))
{
CheckForNull(driver, subKey);
using (var converter = driver.OpenSubKey(Keys.CONVERTER_KEY))
{
CheckForNull(converter, Keys.CONVERTER_KEY);
subKey = "Redirect";
using (var redirect = converter.OpenSubKey(subKey))
{
CheckForNull(redirect, subKey);
userConfig.RedirectEnabled = (int?) redirect.GetValue("Enabled") == 1;
userConfig.RedirectPrinter = redirect.GetValue("Printer").ToString();
userConfig.Format = converter.GetValue("Format").ToString();
var dpiStr = (string)driver.GetValue("RENDER: DPI");
if (dpiStr == null)
{
userConfig.UserRenderDpi = null;
}
else
{
userConfig.UserRenderDpi = double.TryParse(dpiStr, out var dpiVal) ? dpiVal : (double?) null;
}
}
}
}
}
return userConfig;
}
private RegistryView GetRegistryView()
{
if (Environment.Is64BitOperatingSystem)
{
return RegistryView.Registry64;
}
return RegistryView.Registry32;
}
[NotNull]
private string GetSubKey()
{
if (Environment.Is64BitOperatingSystem)
{
return Keys.PRINTER_DRIVER_KEY64;
}
return Keys.PRINTER_DRIVER_KEY32;
}
}
}

View file

@ -0,0 +1,95 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using JetBrains.Annotations;
using VirtualPrinter.Agent.Core;
using VirtualPrinter.Logging;
namespace VirtualPrinter.Utils
{
public class Shell : IShell
{
private readonly IVirtualPrinterLogger<Shell> _logger = new VirtualPrinterLogger<Shell>();
public void WriteIniEntry(string section, string key, string value, string iniFilePath)
{
Win32Sys.WritePrivateProfileString(section, key, value, iniFilePath);
}
public T ReadIniEntry<T>(string section, string key, string iniFilePath)
{
var buffer = new StringBuilder(64 * 1024);
Win32Sys.GetPrivateProfileString(section, key, string.Empty, buffer, buffer.Capacity, iniFilePath);
var value = buffer.ToString().Trim();
return (T)Convert.ChangeType(value, typeof(T));
}
public void Execute(IJobInfo job, ISessionInfo session, string exe, string args)
{
var thr = new Thread
(
() =>
{
try
{
if (!File.Exists(exe))
throw new FileNotFoundException(exe);
#if DEBUG
System.Diagnostics.Process.Start(exe, args);
#else
StartProcessAsUser(job, session, exe, args);
#endif
}
catch (Exception exception)
{
LogError(exception, "Failed to create process");
}
}
);
thr.Start();
}
public bool FileExists(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentException("The path may not be null or empty.");
}
return File.Exists(path);
}
private void StartProcessAsUser([NotNull]IJobInfo job, [NotNull]ISessionInfo session, string exe, string args)
{
if (job == null)
{
throw new ArgumentNullException(nameof(job));
}
if (session == null)
{
throw new ArgumentNullException(nameof(session));
}
var cmd = $@"""{exe}"" {args}";
var id = session.Id;
var user = job.MachineName.TrimStart('\\') + '\\' + job.UserName;
LogDebug($"Executing '{cmd}' for '{user}' ({id})...");
Win32Sys.CreateProcessAsUser(id, user, cmd);
}
private void LogDebug(string message, params object[] args)
{
_logger.Debug(message, args);
}
private void LogError(Exception exception, string message, params object[] args)
{
_logger.Error(exception, message, args);
}
}
}

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{CD1C8E9D-5335-41AC-B0C0-88FD7C7C55F3}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>VirtualPrinter.Utils</RootNamespace>
<AssemblyName>VirtualPrinter.Utils</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\Files\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Consts.cs" />
<Compile Include="DirectoryHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RegistryRepository.cs" />
<Compile Include="Shell.cs" />
<Compile Include="Win32Sys.cs" />
<Compile Include="Windows.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VirtualPrinter.Agent.Core\VirtualPrinter.Agent.Core.csproj">
<Project>{135C85EB-2116-4CC4-8CCB-B6804B9D6467}</Project>
<Name>VirtualPrinter.Agent.Core</Name>
</ProjectReference>
<ProjectReference Include="..\VirtualPrinter.Logging\VirtualPrinter.Logging.csproj">
<Project>{AA25364D-22D5-44B0-86A5-6FB14C686308}</Project>
<Name>VirtualPrinter.Logging</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Cassia" Version="3.0.0-alpha.9" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
<PackageReference Include="System.Security.Principal.Windows" Version="4.7.0" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,239 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Win32.SafeHandles;
namespace VirtualPrinter.Utils
{
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal static class Win32Sys
{
/// <summary>
/// Creates process with given command line via Win32 API.
/// Use this method to start a process as a service.
/// </summary>
/// <param name="sessId"></param>
/// <param name="user"></param>
/// <param name="commandLine"></param>
internal static void CreateProcessAsUser(int sessId, string user, string commandLine)
{
WTSQueryUserToken((uint) sessId, out var userToken);
if (userToken.IsInvalid)
{
throw new InvalidOperationException($"Could not query user token for session {sessId}! (" + GetLastError() + ")",
Windows.LastError);
}
CreateProcessAsUser(userToken, user, commandLine);
}
private static void CreateProcessAsUser([NotNull]SafeTokenHandle token, string user, string commandLine)
{
var processInformation = new PROCESS_INFORMATION();
try
{
var securityAttributes = new SECURITY_ATTRIBUTES();
securityAttributes.Length = Marshal.SizeOf(securityAttributes);
var startupInfo = new STARTUPINFO();
startupInfo.cb = Marshal.SizeOf(startupInfo);
startupInfo.lpDesktop = "winsta0\\default";
var info = new ProfileInfo();
info.dwSize = Marshal.SizeOf(info);
info.lpUserName = user;
info.dwFlags = 1;
var result = LoadUserProfile(token, ref info);
if (!result)
{
throw Windows.LastError;
}
result = CreateEnvironmentBlock(out var lpEnvironment, token, false);
if (!result)
{
throw Windows.LastError;
}
result = CreateProcessAsUser
(
token,
null,
commandLine,
ref securityAttributes,
ref securityAttributes,
false,
0x00000400,
lpEnvironment,
null,
ref startupInfo,
ref processInformation
);
if (!result)
{
throw Windows.LastError;
}
}
finally
{
if (processInformation.hProcess != IntPtr.Zero)
{
CloseMyHandle(processInformation.hProcess);
}
if (processInformation.hThread != IntPtr.Zero)
{
CloseMyHandle(processInformation.hThread);
}
if (!token.IsInvalid)
{
token.Dispose();
}
}
}
#region Classes
public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle() : base(true)
{
}
[DllImport("kernel32.dll")]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
protected override bool ReleaseHandle()
{
return CloseHandle(handle);
}
public override string ToString()
{
return $"{handle}";
}
}
#endregion
#region Structures
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessID;
public int dwThreadID;
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct ProfileInfo
{
public int dwSize;
public int dwFlags;
public string lpUserName;
public string lpProfilePath;
public string lpDefaultPath;
public string lpServerName;
public string lpPolicyPath;
public IntPtr hProfile;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
#endregion
#region Windows interop
[DllImport("userenv.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool LoadUserProfile(SafeTokenHandle hToken, ref ProfileInfo lpProfileInfo);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment,
SafeTokenHandle hToken, bool bInherit);
[DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true,
CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern bool CloseMyHandle(IntPtr handle);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true,
CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
private static extern bool CreateProcessAsUser(SafeTokenHandle hToken,
string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle,
int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation);
[DllImport("wtsapi32.dll", SetLastError = true)]
private static extern bool WTSQueryUserToken(uint sessionId, out SafeTokenHandle Token);
[DllImport("kernel32.dll")]
public static extern uint GetLastError();
[DllImport("kernel32")]
public static extern int GetPrivateProfileString
(
string section,
string key,
string def,
StringBuilder retVal,
int size,
string filePath
);
[DllImport("kernel32")]
public static extern long WritePrivateProfileString
(
string section,
string key,
string val,
string filePath
);
#endregion
}
}

View file

@ -0,0 +1,14 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
namespace VirtualPrinter.Utils
{
public class Windows
{
[NotNull]
public static Exception LastError => new Win32Exception(Marshal.GetLastWin32Error());
}
}