このコードを使用して、PDFファイルをC#のローカルプリンターでWindowsサービス内に印刷します。
Process process = new Process();
PrinterSettings printerSettings = new PrinterSettings();
if (!string.IsNullOrWhiteSpace(basePrint))
printerSettings.PrinterName = basePrint;
process.StartInfo.FileName = fileName;
process.StartInfo.Verb = "printto";
process.StartInfo.Arguments = "\"" + printerSettings.PrinterName + "\"";
process.Start();
process.WaitForInputIdle();
ユーザーがWindowsサービスを実行するように設定すると、すべてが正常に機能します。
LocalSystem資格情報の下でこのコードを実行すると、「この操作に関連付けられたアプリケーションはありません」というエラーが表示されますが、これは通常、拡張子が.pdfのファイルの印刷操作を処理するプログラムがないことを示しています。 。
私の問題は、このコードがサービスに設定された特定のユーザーで機能し、ファイルを右クリックすることでプリンターにファイルを送信できるという事実によって確認されるように、この操作を処理するプログラム(Foxit Reader)があることです印刷オプションを選択します。
特定のユーザーなしでサービス内からローカルプリンターで印刷できるように変更できるものはありますか?
PDFアプリケーションがシステム全体のPATH変数ではなく、特定のユーザーの下にあるのでしょうか?
「ローカルシステム」のユーザーが適切なアプリケーションを見つけられないために問題が発生すると思います。そのため、アプリケーションを登録する必要があります。すでに別の回答を受け入れているので、これ以上時間を割くことはしませんが、これについてさらに質問がある場合は、質問してください。
おそらく作業コードを実行できますが、現在アクティブなセッションユーザートークンを使用します(ただし、アクティブセッションがない場合、これは機能しません)
簡潔にするために、このコードはコンパイルできません。最初にいくつかの P/Invoke を調整して追加する必要があります。
アクティブなセッションIDを見つける必要があります。ローカルで開かれたセッションの場合、これを使用します:
_ [DllImport("wtsapi32.dll", SetLastError = true)]
public static extern int WTSEnumerateSessions(
IntPtr hServer,
int Reserved,
int Version,
ref IntPtr ppSessionInfo,
ref int pCount);
_
次に、開いているセッションIDを検索します。
_ var typeSessionInfo = typeof(WTSApi32.WTSSessionInfo);
var sizeSessionInfo = Marshal.SizeOf(typeSessionInfo);
var current = handleSessionInfo;
for (var i = 0; i < sessionCount; i++)
{
var sessionInfo = (WTSApi32.WTSSessionInfo)Marshal.PtrToStructure(current, typeSessionInfo);
current += sizeSessionInfo;
if (sessionInfo.State == WTSApi32.WTSConnectStateClass.WTSActive)
return sessionInfo.SessionID;
}
_
見つからない場合は、次でrdpセッションを検索します。
_ [DllImport("kernel32.dll")]
public static extern uint WTSGetActiveConsoleSessionId();
_
それでトークンを取得
_ private static IntPtr GetUserImpersonatedToken(uint activeSessionId)
{
if (!WTSApi32.WTSQueryUserToken(activeSessionId, out var handleImpersonationToken))
Win32Helper.RaiseInvalidOperation("WTSQueryUserToken");
try
{
return DuplicateToken(handleImpersonationToken, AdvApi32.TokenType.TokenPrimary);
}
finally
{
Kernel32.CloseHandle(handleImpersonationToken);
}
}
_
これにより、開いたユーザーセッションでローカルシステムサービスからexeを実行できます。
_ public static void ExecuteAsUserFromService(string appExeFullPath, uint activeSessionId, bool isVisible = false, string cmdLine = null, string workDir = null)
{
var tokenUser = GetUserImpersonatedToken(activeSessionId);
try
{
if (!AdvApi32.SetTokenInformation(tokenUser, AdvApi32.TokenInformationClass.TokenSessionId, ref activeSessionId, sizeof(UInt32)))
Win32Helper.RaiseInvalidOperation("SetTokenInformation");
ExecuteAsUser(tokenUser, appExeFullPath, isVisible, cmdLine, workDir);
}
finally
{
Kernel32.CloseHandle(tokenUser);
}
}
_
コードをCreateProcessAsUSer(...)
に適合できるかどうかを確認します
_ private static void ExecuteAsUser(IntPtr token, string appExeFullPath, bool isVisible, string cmdLine, string workDir)
{
PrepareExecute(appExeFullPath, isVisible, ref workDir, out var creationFlags, out var startInfo, out var procInfo);
try
{
startInfo.lpDesktop = "WinSta0\\Default";
var processAttributes = new AdvApi32.SecurityAttributes
{
lpSecurityDescriptor = IntPtr.Zero
};
var threadAttributes = new AdvApi32.SecurityAttributes
{
lpSecurityDescriptor = IntPtr.Zero
};
if (!AdvApi32.CreateProcessAsUser(token,
appExeFullPath, // Application Name
cmdLine, // Command Line
ref processAttributes,
ref threadAttributes,
true,
creationFlags,
IntPtr.Zero,
workDir, // Working directory
ref startInfo,
out procInfo))
{
throw Win32Helper.RaiseInvalidOperation("CreateProcessAsUser");
}
}
finally
{
Kernel32.CloseHandle(procInfo.hThread);
Kernel32.CloseHandle(procInfo.hProcess);
}
}
_
このコードがあなたや他の誰かに役立つことを願っています!
問題は、SYSTEM(LocalSystem)アカウントのユーザーインターフェイス機能が制限されており、シェルまたはシェル拡張が削除または無効にされている可能性があります。また、動詞はシェルサブシステム、特にエクスプローラーの機能です。
プログラムを手動で起動して、そうであるか、それともセキュリティの問題であるか、ユーザープロファイルの詳細がないかを確認できます。
これを行うには、レジストリを掘り下げる必要があります。シェル実行拡張機能の動詞の多くにコマンドラインがあることがわかります。
たとえば、HKEY_CLASSES_ROOT.pdf\Shell\printto\commandを見つけてそのコマンドを使用します。
また、SYSTEMアカウントがこのキーと親キーにアクセスできることを確認することもできます。 (まれなケースですが、確認する価値があります)