I need a way to expand environment variable on a remote machine.
Suppose I have a path to a folder %appdata%\MyApp\Plugins or %ProgramFiles%\MyCompany\MyApp\Plugins and I want to list files in that folder for audit purposes. The only problem is I want to do it on a remote machine, which however I have admin access to.
An extra question (but not essential) is how to do that for given user on remote machine?
You would use GetFolderPath. There are a bunch of different SpecialFolder values that you could use including ProgramFiles and ApplicationData
string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
Then you could just combine it with the rest of your path
string full_path = Path.Combine(path, "\MyApp\Plugins");
On a remote machine, it looks like you can try something like this
ConnectionOptions co = new ConnectionOptions();
// user with sufficient privileges to connect to the cimv2 namespace
co.Username = "administrator";
// his password
co.Password = "adminPwd";
ManagementScope scope = new ManagementScope(#"\\BOBSMachine\root\cimv2", co);
SelectQuery query = new SelectQuery("Select windowsdirectory from Win32_OperatingSystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
foreach (ManagementObject windir in searcher.Get())
Console.WriteLine("Value = {0}", windir["windowsdirectory"]);
Or for a list of all remote environment variables and their values, from here
public static void GetSysInfo(string domain, string machine, string username, string password)
{
ManagementObjectSearcher query = null;
ManagementObjectCollection queryCollection = null;
ConnectionOptions opt = new ConnectionOptions();
opt.Impersonation = ImpersonationLevel.Impersonate;
opt.EnablePrivileges = true;
opt.Username = username;
opt.Password = password;
try
{
ManagementPath p = new ManagementPath("\\\\" +machine+ "\\root\\cimv2");
ManagementScope msc = new ManagementScope(p, opt);
SelectQuery q= new SelectQuery("Win32_Environment");
query = new ManagementObjectSearcher(msc, q, null);
queryCollection = query.Get();
Console.WriteLine(queryCollection.Count);
foreach (ManagementBaseObject envVar in queryCollection)
{
Console.WriteLine("System environment variable {0} = {1}",
envVar["Name"], envVar["VariableValue"]);
}
}
catch(ManagementException e)
{
Console.WriteLine(e.Message);
Environment.Exit(1);
}
catch(System.UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
Environment.Exit(1);
}
}
OP Edit:
Also %AppData% can be found from registry (can be done remotely) at HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders and Program Files at HKLM\Software\Microsoft\Windows\CurrentVersion, under ProgramfilesDir.
The question doesn't make sense. Environment variables are not per-machine variables. For instance, you can expect %appdata% to point inside the C:\users\ directory, but precisely where obviously depends to the user. Logging in as admin still doesn't help you; that would merely tell you where the admin's %appdata% is.
Environment variables are the amalgamation of 'puter-wide and per-user settings. A running process may modify its environment and when it spawns another process, that process inherits the environment of the process that created it.
Unless you have access to a process running on the remote machine (or can start one), there's no such thing as an 'environment': the context for it simply doesn't exist. The environment of a particular process is a function of all of the following:
the environment inherited from the parent process' environment (which may be running under a different user account than the child process.)
computer-wide environment settings.
any environment settings specified by the user.
any changes made by the process itself.
That being said, Windows keeps its environment variable settings in the registry:
User variables.HKEY_CURRENT_USER\Environment
System variables.HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
If you have appropriate access to the remote machine's registry, you should be able to fish out what you need.
Note that environment variables may be defined in terms of other environment variables: I believe you'll likely to take care of the proper expansion yourself.
As far as I can tell, the only way of resolving %ProgramFiles% is via the registry, since this is not exposed in Win32_Environment (despite the documentation suggesting otherwise). So this works fine:
$key = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$serverName);
$versionKey = $key.OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion');
$versionKey.GetValue('ProgramFilesDir')
However, I can't appear to use this approach to get back the Program Files (x86) folder - the key I can see in the registry doesn't 'show' using the registry API. Strange.
Of course if you were running Powershell Remoting on the remote machine, I imagine this would be fairly easy...
Related
I have a windows application. In that i have retrieved appdata using environment variable. So it gives me following path
c:\document and settings\current user name\application data.
But when I retrieve the appdata path from windows service using environment variable i get following path
c:\windows\ServiceProfiles\LocalService\AppData\Local
so this appdata path is different from appdata path that i got from windows application environments variable appdata path.
I m running windows service under local profile. I know that if i change service profile to run under user then service appdata path and windows application appdata path matches but service prompts for username and password.
so my question is how to get user appdata path from service by running service under local profile without prompting for username and password?
I have also encountered in that problem and looked at your question but i at first sight didnot find an answer in it.
This is the Mohit shah Answer
"I found out that is not possible to get user appdata path from windows service by running service under profile "LocalSystem". So I used Environment.SpecialFolder.CommomAppData which gives me app data path C:\ProgramData when run on windows 7 and when used same thing in windows service, it also gived the same path and i also ran the service under profile "LocalSystem" so it did not prompt me for credentials. So this solved my problem."
#Mohit Shah Please mark this is as answer so that other can take help from that.
Windows service will always runs on SYSTEM level and hence it wont able to access user specific folder. Either as #ovais suggested you can store user data inside program data folder or you can use following approach.
You can use Windows management API's to get the current windows user name.Usually remaining path will be constant and hence you can construct remaining path.
Say for example, data is stored inside - "C:\Users\xyzUser\appdata\roaming..."
Only thing which is not constant here is "xyzUser" and "C"(User can install in different drives).
public static string GetWindowsUserAccountName()
{
string userName = string.Empty;
ManagementScope ms = new ManagementScope("\\\\.\\root\\cimv2");
ObjectQuery query = new ObjectQuery("select * from win32_computersystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(ms, query);
foreach (ManagementObject mo in searcher?.Get())
{
userName = mo["username"]?.ToString();
}
userName = userName?.Substring(userName.IndexOf(#"\") + 1);
return userName;
}
Drawback of this approach is, when you connected through remote connection, username will give you "NULL". So please be careful while using .
Windows folder you can get through following snippet.
public static string GetWindowsFolder()
{
string windowsFolder = string.Empty;
ManagementScope ms = new ManagementScope("\\\\.\\root\\cimv2");
ObjectQuery query = new ObjectQuery("SELECT * FROM Win32_OperatingSystem");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(ms, query);
foreach (ManagementObject m in searcher?.Get())
{
windowsFolder = m["WindowsDirectory"]?.ToString();
}
windowsFolder = windowsFolder.Substring(0, windowsFolder.IndexOf(#"\"));
return windowsFolder;
}
I am trying to come up with a good way to enumerate hard disks on remote workstations, possibly including administrative shares, so I can audit key files on them without having to access them via sneakernet. I have domain administrator rights. Security policy prohibits using WMI which would be a great solution.
I can retrieve a list of computers using Active Directory, but I need some way to determine what drives are available on each system. Is such a thing feasible? A fellow developer offered some VB6 code from years ago that used WNetOpenEnum, but I was hoping that since we are at .NET framework 4, maybe there's a more elegant / managed way of working with this.
Any ideas would be much appreciated!
EDIT:
I'm keen to use technologies that are more generally supported, such as standard APIs etc. WMI is a great solution but apparently is blocked by default by Windows Firewall, so its availability is not guaranteed.
Add a reference to System.Management, then:
using System;
using System.Management;
namespace WmiConnectRemote
{
class Program
{
static void Main(string[] args)
{
var machine = "XXXX";
var options = new ConnectionOptions { Username = "XXXX", Password = "XXXX" };
var scope = new ManagementScope(#"\\" + machine + #"\root\cimv2", options);
var queryString = "select Name, Size, FreeSpace from Win32_LogicalDisk where DriveType=3"; var query = new ObjectQuery(queryString);
var worker = new ManagementObjectSearcher(scope, query);
var results = worker.Get();
foreach (ManagementObject item in results)
{
Console.WriteLine("{0} {2} {1}", item["Name"], item["FreeSpace"], item["Size"]);
}
}
}
}
How can I start a process on a remote computer in c#, say computer name = "someComputer", using System.Diagnostics.Process class?
I created a small console app on that remote computer that just writes "Hello world" to a txt file, and I would like to call it remotely.
Console app path: c:\MyAppFolder\MyApp.exe
Currently I have this:
ProcessStartInfo startInfo = new ProcessStartInfo(string.Format(#"\\{0}\{1}", someComputer, somePath);
startInfo.UserName = "MyUserName";
SecureString sec = new SecureString();
string pwd = "MyPassword";
foreach (char item in pwd)
{
sec.AppendChar(item);
}
sec.MakeReadOnly();
startInfo.Password = sec;
startInfo.UseShellExecute = false;
Process.Start(startInfo);
I keep getting "Network path was not found".
Can can use PsExec from http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx
Or WMI:
object theProcessToRun() = { "YourFileHere" };
ManagementClass theClass = new ManagementClass(#"\\server\root\cimv2:Win32_Process");
theClass.InvokeMethod("Create", theProcessToRun);
Use one of the following:
(EDIT) Remote Powershell
WMI (see Ivan G's answer)
Task Scheduler API (http://msdn.microsoft.com/en-us/library/windows/desktop/aa383606%28v=vs.85%29.aspx)
PsExec
WshRemote object with a dummy script. Chances are, it works via DCOM, activating some of scripting objects remotely.
Or if you feel like it, inject your own service or COM component. That would be very close to what PsExec does.
Of all these methods, I prefer task scheduler. The cleanest API of them all, I think. Connect to the remote task scheduler, create a new task for the executable, run it. Note: the executable name should be local to that machine. Not \servername\path\file.exe, but c:\path\file.exe. Delete the task if you feel like it.
All those methods require that you have administrative access to the target machine.
ProcessStartInfo is not capable of launching remote processes.
According to MSDN, a Process object only allows access to remote processes not the ability to start or stop remote processes. So to answer your question with respect to using this class, you can't.
An example with WMI and other credentials as the current process, on default it used the same user as the process runs.
var hostname = "server"; //hostname or a IpAddress
var connection = new ConnectionOptions();
//The '.\' is for a local user on the remote machine
//Or 'mydomain\user' for a domain user
connection.Username = #".\Administrator";
connection.Password = "passwordOfAdministrator";
object[] theProcessToRun = { "YourFileHere" }; //for example notepad.exe
var wmiScope = new ManagementScope($#"\\{hostname}\root\cimv2", connection);
wmiScope.Connect();
using (var managementClass = new ManagementClass(wmiScope, new ManagementPath("Win32_Process"), new ObjectGetOptions()))
{
managementClass.InvokeMethod("Create", theProcessToRun);
}
I don't believe you can start a process through a UNC path directly; that is, if System.Process uses the windows comspec to launch the application... how about you test this theory by mapping a drive to "\someComputer\somePath", then changing your creation of the ProcessStartInfo to that? If it works that way, then you may want to consider temporarily mapping a drive programmatically, launch your app, then remove the mapping (much like pushd/popd works from a command window).
I have two machines, Say Machine1 and Machine2. I have mapped one shared folder in Machine2 in Machine1 as Z.
I started one application say App1 which list all drives of machine and the application is in Machine1. I started this App1 from Machine1 and I got all my drives
Including the mapped drive 'Z'. But when I Started the App1 which is in Machine1 from Machine2 using WMI [Windows Management Instrumentation. Code used for WMI communication is given below.]. App1 lists all drives except the mapped network drive 'Z'.
Is there any possible way to list out all drives of Machine1 even if I start App1 from Machine2 using WMI.
// Code to start application on remote machine.
ManagementPath path = new ManagementPath( string.Format( #"\\{0}\root\cimv2:Win32_Process", machine ) );
try
{
// Connect options include user name and password of the remote machine to connect.
ConnectionOptions options = new ConnectionOptions();
options.Username = UserName;
options.Password = Password;
ManagementPath path = new ManagementPath( string.Format( #"\\{0}\root\cimv2:Win32_Directory", machine ) );
ManagementScope scope = new ManagementScope( path, options );
if( scope != null )
{
ObjectGetOptions getOptions = new ObjectGetOptions();
ManagementClass cimInstance = new ManagementClass( scope, path, getOptions );
// Fill the necessary parameters for Create method of Win32 Process.
ManagementBaseObject inParams = cimInstance.GetMethodParameters( "Create" );
// Commandline is the full path of the application including the exe name.
inParams["CommandLine"] = commandLine;
// Execute the method and obtain the return values.
cimInstance.InvokeMethod( "Create", inParams, null );
}
}
Me trying using .Net 2.0 Windows.
Mapped drives are a per-user setting. When you run the app from the other machine, it probably uses a different user account.
We have an application that installs SQL Server Express from the command line and specifies the service account as the LocalSystem account via the parameter SQLACCOUNT="NT AUTHORITY\SYSTEM".
This doesn't work with different languages because the account name for LocalSystem is different. There's a table listing the differences here:
http://forums.microsoft.com/MSR/ShowPost.aspx?PostID=685354&SiteID=37
This doesn't seem to be complete (the Swedish version isn't listed). So I'd like to be able to determine the name programmatically, perhaps using the SID?
I've found some VB Script to do this:
Set objWMI = GetObject("winmgmts:root\cimv2")
Set objSid = objWMI.Get("Win32_SID.SID='S-1-5-18'")
MsgBox objSid.ReferencedDomainName & "\" & objSid.AccountName
Does anyone know the equivalent code that can be used in C#?
You can use .NET's built-in System.Security.Principal.SecurityIdentifier class for this purpose: by translating it into an instance of NtAccount you can obtain the account name:
using System.Security.Principal;
SecurityIdentifier sid = new SecurityIdentifier("S-1-5-18");
NTAccount acct = (NTAccount)sid.Translate(typeof(NTAccount));
Console.WriteLine(acct.Value);
Later edit, in response to question in comments: you do not need any special privileges to do SID-to-name lookups on the local machine -- for example, even if the user account you're running under is only in the Guests group, this code should work. Things are a little bit different if the SID resolves to a domain account, but even that should work correctly in most cases, as long as you're logged on to the domain (and a domain controller is available at the time of the lookup).
Or you can use:
string localSystem = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null).Translate(typeof(NTAccount)).Value;
With WellKnownSidType you can look for other accounts, as NetworkService for example.
This should do something similar to what you posted. I'm not sure how to get specific properties of WMI objects offhand, but this will get you started with the syntax:
ManagementObject m = new ManagementObject("winmgmts:root\cimv2");
m.Get();
MessageBox.Show(m["Win32_SID.SID='S-1-5-18'"].ToString());
The problem with the accepted answer is that the account name must be resolvable by the local machine running the code.
If you are reading the ACLs on a remote machine you may well not be able to resolve Domain SIDs / local SIDs on the remote box. The following uses WMI and takes the parameter of the remote machine and the SID you want the remote machine to resolve.
/// <summary>
/// Returns the Account name for the specified SID
// using WMI against the specified remote machine
/// </summary>
private string RemoteSID2AccountName(String MachineName, String SIDString)
{
ManagementScope oScope = new ManagementScope(#"\\" + MachineName +
#"\root\cimv2");
ManagementPath oPath = new ManagementPath("Win32_SID.SID='" + SIDString + "'");
ManagementObject oObject = new ManagementObject(oScope, oPath, null);
return oObject["AccountName"].ToString();
}