Reputation: 201
I'm trying to execute a labview VI, launching that from a .bat file, called via ssh, from another Windows machine. So I do
ssh myuser@IP
cd Desktop
launchVis.bat
I connect with user and password. myuser have all the rights to launch the batch file. Meanwhile I check the execution via RDP connection on the same machine.
If I run the bat file from a cmd line on the remote machine, the VI starts normally If I run the bat file from the ssh connection, i can see the output of echoes in the bat file but LabVIEW will be launched in a different session from RDP-TCP#1. The result is that I can see a "LabVIEW" process started in a Session named Services, but I cannot see the VI executing and in general, I don't know IF is executing or not.
Googling about the problem, It seems that I cannot avoid to start processes in "Services" Session and, for this reason, I cannot launch any GUI Program via SSH. Suggested solutions are using PsExec or, maybe a third part of ssh Server (with a third part ssh Server I reached my goal over Windows 7)
Upvotes: 11
Views: 22008
Reputation: 32659
The best way I've found is creating a scheduled task and invoking it, see https://serverfault.com/a/1160137/30441
Including the actual answer from that link here (credit to Kristian Dimitrov)
You can do that. Let's say you are connected to a remote machine via ssh with user 'office' and you want to run Mozzila Firefox. You have to first create a scheduled task:
$User = "office"
$Firefox = New-ScheduledTaskAction -Execute "C:\Program Files\Mozilla Firefox\firefox.exe"
Register-ScheduledTask -TaskName "Firefox" -User $User -Action $Firefox
Then you can execute it by running the following command:
Start-ScheduledTask -TaskName "Firefox"
Upvotes: 0
Reputation: 1
I solved this problem with these two scripts and the PsExeс tool. Just download the tool and place it with the powershell and batch scripts in the user folder. And after that you will be able to run GUI applications directly through SSH by calling RunGUIAppFromSSH.bat your_program parameters.
Note that for this solution, you don't need to run the SSH server as a user (it will work with the standard OpenSSH server installed in the usual way).
StartProcessAsCurrentUser.ps1
param(
[Parameter(Mandatory=$true)][string]$Program,
[string]$cmdline = ''
)
$Source = @"
using System;
using System.Runtime.InteropServices;
namespace murrayju.ProcessExtensions
{
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
const string SE_TCB_NAME = "SeTcbPrivilege";
const int SE_PRIVILEGE_ENABLED = 0x00000002;
const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
}
[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern Int32 GetLastError();
[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("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
[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);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
private static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
#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((IntPtr)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;
IntPtr LoggedInUserToken = IntPtr.Zero;
startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO));
try
{
if (!GetSessionUserToken(ref hUserToken))
{
throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed. ErrCode: " + GetLastError().ToString());
}
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))
{
throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed.\n");
}
iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();
}
finally
{
CloseHandle(hUserToken);
if (pEnv != IntPtr.Zero)
{
DestroyEnvironmentBlock(pEnv);
}
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
}
return true;
}
}
}
"@
Add-Type -ReferencedAssemblies 'System', 'System.Runtime.InteropServices' -TypeDefinition $Source -Language CSharp
[murrayju.ProcessExtensions.ProcessExtensions]::StartProcessAsCurrentUser($Program, $cmdline)
RunGUIAppFromSSH.bat
@echo off
%~dp0\PsExec.exe -sd powershell %~dp0\StartProcessAsCurrentUser.ps1 '%1' '%2'
How it works?
The StartProcessAsCurrentUser.ps1 script calls the special WinAPI function CreateProcessAsUser, which allows us to run any GUI application from the user's perspective. We need to run this script via the PsExec tool because some of the WinAPI functions used require being called from the system context, which is why the -s flag is necessary. Additionally, some functions require the SE_TCB_NAME privilege. I didn’t encounter issues with this, but if you get a 1314 error, I recommend reading more about it.
Upvotes: 0
Reputation: 137
What helped me solve this issue is learning that running sshd in "interactive mode" is equivalent to running sshd as a regular user (not root). It's not something I've had to ever do on Linux, but once defined this way it's easier to find help online.
There are three issues with running sshd as an unprivileged user:
/etc/ssh/ssh_host_{ecdsa,ed25519,rsa,dsa}_key{,.pub}
ssh_host_ecdsa_key
Open cmd prompt in %userprofile%\.ssh
ssh-keygen -q -N "" -t dsa -f ./ssh_host_dsa_key
ssh-keygen -q -N "" -t rsa -b 4096 -f ./ssh_host_rsa_key
ssh-keygen -q -N "" -t ecdsa -f ./ssh_host_ecdsa_key
ssh-keygen -q -N "" -t ed25519 -f ./ssh_host_ed25519_key
Copy %programdata%\ssh\sshd_config
to %userprofile%\ssh
Port <BETWEEN-1024-AND-65535>
HostKey C:\Users\<USER>\.ssh\ssh_host_rsa_key
HostKey C:\Users\<USER>\.ssh\ssh_host_dsa_key
HostKey C:\Users\<USER>\.ssh\ssh_host_ecdsa_key
HostKey C:\Users\<USER>\.ssh\ssh_host_ed25519_key
PidFile C:\Users\<USER>\.ssh\sshd.pid
netsh advfirewall firewall add rule name="Open Port <BETWEEN-1024-AND-65535>" dir=in action=allow protocol=TCP localport=<BETWEEN-1024-AND-65535>
Create sshd-interactive-mode.bat
in the Startup
* folder:
start "" C:\Users\user\bin\SilentCMD\SilentCMD.exe "C:\Program Files\OpenSSH\sshd.exe" -f C:\Users\user\.ssh\sshd_config
SilentCMD spawns it as a background task. Download it and save to =%userprofile%\bin\SilentCMD=
start ""
prevents cmd prompt from lingering on the desktop after sshd exits
.ssh
directory, but you may choose a separate directory.C:\Users\<USER>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
Upvotes: 0
Reputation: 201
The "solution", using a third party ssh server is this:
In that way, the ssh server will be launched as user and CAN execute GUI programs.
Unfortunately, I didn't manage to achieve the same goal with the Windows official OpenSSH server, because it fail to be launched as User, but seems working only as a Service
Upvotes: 8
Reputation: 1578
Running SSHd as a service makes launching desktop applications difficult, because a service has no access to the user desktop (Windows Station, WinSta0
) link
Option 1: Start SSHd as a user
fork of unprivileged child failed
, as running SSHd as a user is no more possible since OpenSSH 7.5 released in 2017 linkOption 2: Use a launcher that has access to the Windows Station
On Windows, there are 2 options left to launch desktop applications over SSH: link to Github OpenSSH issue
Usage:
ssh user@host "psexec -i 1 mydesktoplauncher.bat"
See psexec documentation for optional arguments like -i 1
Upvotes: 12