WMI access to UNC paths - c#

I'm developing a server C# application which executes a .exe file through a local domain network on different client computers.
I've chosen to do it via WMI and works fine when the .exe path is local to the remote machine. Searching over other threads here and other forums I've noticed that WMI does not support UNC paths (here comes my problem).
When I call the method below to execute a .exe placed on the remote pc desktop, it just works fine:
var execResult = WmiExecuteRemoteProcess("XPSP3", #"C:\Documents and Settings\user1\Desktop\My_Setup.exe", #"domain\user", "mypass");
Now, when I try to use UNC paths, I get the exit code 2:
var execResult = WmiExecuteRemoteProcess("XPSP3", #"\\server\shared\My_Setup.exe", #"domain\user", "mypass");
The WmiExecuteRemoteProcess method looks like this:
public bool WmiExecuteRemoteProcess(string remoteComputerName, string arguments, string pUser, string pPassword)
{
try
{
ConnectionOptions connOptions = new ConnectionOptions();
connOptions.Username = pUser;
connOptions.Password = pPassword;
connOptions.Impersonation = ImpersonationLevel.Impersonate;
connOptions.EnablePrivileges = true;
ManagementScope manScope = new ManagementScope(string.Format(#"\\{0}\ROOT\CIMV2", remoteComputerName), connOptions);
manScope.Connect();
ObjectGetOptions objectGetOptions = new ObjectGetOptions();
ManagementPath managementPath = new ManagementPath("Win32_Process");
using (ManagementClass processClass = new ManagementClass(manScope, managementPath, objectGetOptions))
{
using (ManagementBaseObject inParams = processClass.GetMethodParameters("Create"))
{
inParams["CommandLine"] = arguments;
using (ManagementBaseObject outParams = processClass.InvokeMethod("Create", inParams, null))
{
return (uint)outParams["returnValue"] == 0;
}
}
}
}
catch (Exception ex)
{
Log.Error(ex.Message);
return false;
}
}
Given this situation I've decided kind of "cheat" it by parsing the arguments parameter as follows:
var args = "cmd.exe /c \"pushd \"\"\\\\server\\shared\"\" && My_Setup.exe && popd\"";
var execResult = WmiExecuteRemoteProcess("XPSP3",args,#"domain\user", "mypass");
What I try to do here is to use the cmd.exe with the commands pushd and popd to map the UNC path into a network drive-based path like "Z:\shared". This way both WMI and cmd.exe don't have to deal with the UNC path.
Result: again, if the .exe is local to the remote machine, it works very well, but when using a UNC path, only the cmd.exe process appears. Maybe it's internally throwing the exit code 2 again, but I'm not able to catch it, even redirecting the output of the cmd execution to a log file.
Perhaps someone experienced in this kind of mechanics can throw some light on this. I'd prefer not to develop an entire service only for this, or to use PsExec (maybe this one as a last resort).
Please let me know if I'm missing any info. Any comments will be much appreciated.
Regards.
Edit: I checked and it's not a matter of permissions to the shared folder or file.

In case anyone faces this or other related issue, this is how I solved it:
The issue is not that WMI cannot deal with UNC paths, but WMI operations are not allowed to access network resources due to security restrictions in Windows. It doesn't matter if you map the paths, it's just not authorized. In this particular case the workaround I ended up with, was to copy the setup.exe to a temporary folder in the remote machine, and finally execute it through WMI by accessing its local path just like I was doing before.
var execResult = WmiExecuteRemoteProcess("XPSP3", #"C:\temp_folder\My_Setup.exe", #"domain\user", "mypass");

Related

How to run a command prompt on a shared server? [duplicate]

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).

How to share a folder on Windows 7

I am using C# to create a folder and share it on the network:
if (!System.IO.Directory.Exists(FolderPath))
{
System.IO.Directory.CreateDirectory(FolderPath);
// Calling Win32_Share class to create a shared folder
ManagementClass managementClass = new ManagementClass("Win32_Share");
// Get the parameter for the Create Method for the folder
ManagementBaseObject inParams = managementClass.GetMethodParameters("Create");
ManagementBaseObject outParams;
// Assigning the values to the parameters
inParams["Description"] = Description;
inParams["Name"] = ShareName;
inParams["Path"] = FolderPath;
inParams["Type"] = 0x0;
// Finally Invoke the Create Method to do the process
outParams = managementClass.InvokeMethod("Create", inParams, null);
// Validation done here to check sharing is done or not
if ((uint)(outParams.Properties["ReturnValue"].Value) != 0)
{
//MessageBox.Show("Folder might be already in share or unable to share the directory");
}
}
It works on XP, but I am not able to share a folder from this code on Windows 7.
Can anyone tell me how I can share a folder in Windows 7 using C#?
Your application will need to be running with Administrative access in order to share a folder.
If you have a look at the following link, it talks about what appears to be the same situation as what you are dealing with. About half way down there is an accepted answer which walks through the additional actions required for it to work on a Windows 7 machine.
Link: http://social.msdn.microsoft.com/Forums/en-US/windowssdk/thread/de213b61-dc7e-4f33-acdb-893aa96837fa
Hope this helps
Replace the
\
in your path with
/

How to alter the "returnValue" property of ManagementBaseObject after running a batch file?

The situation is: I must use ManagementBaseObject to run a batch file and get its return value (like, 0 if the commands were properly executed, 1 if something went wrong). I've tried to use "EXIT" command to set an errorlevel code, but no matter what I put as the exit code, the "returnValue" property is always 0. Is there any way to make the batch file affect that property?
here is the code (instead of calling the batch file I am just executing a simple command line to make it simpler to post here, but the idea is the same):
ManagementClass processClass = new ManagementClass("Win32_Process");
try
{
ManagementBaseObject inp = processClass.GetMethodParameters("Create");
inp["CommandLine"] = #"cmd /c exit /B 1";
ManagementBaseObject outp;
outp = processClass.InvokeMethod("Create", inp, null);
Console.WriteLine(outp["returnValue"]);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
...but the output is always 0! What I wanted to see is 1, which is what I put as the exit code. Is it possible to do that?
I know I "could" use the Process class to make it work, but using ManagementBaseObject was the only way I could find to run a batch file on a remote machine.
To give a little bit of context:
I have this batch file located on a remote machine and I must run that file from my local machine. I am currently using the classes ManagementScope and ConnectionOptions to connect to the remote machine providing my credentials. After that, I create a ManagementClass associated with the remote connection, with ManagementPath set as "Win32_Process". Then, I use ManagementBaseObject to run the batch file and get its return value.
If anyone knows any other way to do this please let me know, I'm open to suggestions.
Thanks!

Access the Windows 7 Boot Configuration Data using C#

I need to be able to access the identifier GUID of the current running installation of Windows from the Boot Configuration Data Store using c#. It can be returned from the command line running:
bcdedit /enum {current} /v
The problem I have is that in c# if I try to directly run this command (even though the program is running as Administrator) I'm told that bcdedit does not exist. I'm using:
ProcessStartInfo procStartInfo = new ProcessStartInfo("bcdedit.exe", "/enum {current} /v");
The other thing that I have researched is using WMI but the only reference I have to doing so is http://msdn.microsoft.com/en-us/library/windows/desktop/aa362673(v=vs.85).aspx which isn't very helpful.
The best solution would be if I don't have to use bcdedit but instead could use native WMI classes. How would I find the current Windows Boot Loader identifier using C#?
There seem to be many problems accessing bcdedit.exe directly but I was able to figure out how to use WMI in C# to access the BcdStore:
ConnectionOptions connectionOptions = new ConnectionOptions();
connectionOptions.Impersonation = ImpersonationLevel.Impersonate;
connectionOptions.EnablePrivileges = true;
// The ManagementScope is used to access the WMI info as Administrator
ManagementScope managementScope = new ManagementScope(#"root\WMI", connectionOptions);
// {9dea862c-5cdd-4e70-acc1-f32b344d4795} is the GUID of the System BcdStore
ManagementObject privateLateBoundObject = new ManagementObject(managementScope, new ManagementPath("root\\WMI:BcdObject.Id=\"{9dea862c-5cdd-4e70-acc1-f32b344d4795}\",StoreFilePath=\"\""), null);
ManagementBaseObject inParams = null;
inParams = privateLateBoundObject.GetMethodParameters("GetElement");
// 0x24000001 is a BCD constant: BcdBootMgrObjectList_DisplayOrder
inParams["Type"] = ((UInt32)0x24000001);
ManagementBaseObject outParams = privateLateBoundObject.InvokeMethod("GetElement", inParams, null);
ManagementBaseObject mboOut = ((ManagementBaseObject)(outParams.Properties["Element"].Value));
string[] osIdList = (string[]) mboOut.GetPropertyValue("Ids");
// Each osGuid is the GUID of one Boot Manager in the BcdStore
foreach (string osGuid in osIdList)
{
ManagementObject currentManObj = new ManagementObject(managementScope, new ManagementPath("root\\WMI:BcdObject.Id=\"" + osGuid + "\",StoreFilePath=\"\""), null);
MessageBox.Show("" + currentManObj.GetPropertyValue("Id"));
}
This gets the GUID of every Windows Boot Manager in the BcdStore and shows them in a MessageBox. It should be noted that you must have the right ConnectionOptions and that this program must be run as Administrator.
Thanks to Ross Johnston for his project at: http://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=18233 to find the BCD constants and to Tran Dinh Hop for his project at: http://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=19208 which has all of the C# code to work with the BcdStore (except for the aforementioned constants).
Update:
Using:
ManagementObject privateLateBoundObject = new ManagementObject(managementScope, new ManagementPath("root\\WMI:BcdObject.Id=\"{fa926493-6f1c-4193-a414-58f0b2456d1e}\",StoreFilePath=\"\""), null);
will obtain the BcdObject for the current, running Windows Boot Manager. If you then call:
currentManObj.GetPropertyValue("Id")
you will get the GUID of the current, running Windows Boot Manager which is different from "{fa926493-6f1c-4193-a414-58f0b2456d1e}" which is a link to the current Boot Manager.
Thanks to The Microsoft Scripting Guys and their project at: http://technet.microsoft.com/en-us/magazine/2008.07.heyscriptingguy.aspx?pr=blog for having that GUID constant that links to the current Boot Manager.
Note that there is only a 64-bit bcdedit.exe in %systemroot%\system32. If your app is 32-bit, it will not be able to launch the 64-bit bcdedit because the WOW64 layer remaps the system32\ directory to syswow64. It's definitely best to use the WMI interface.

Manage processes on local machine using ASP .Net

I am trying to execute a process in a remote server from an ASP .Net web application (Note that the web application resides on the same server as the processes). The code I am running is the following:
ConnectionOptions conO = new ConnectionOptions();
// conO.Username = txtUser.Text;
// conO.Password = txtPassword.Text;
ManagementPath path = new ManagementPath(#"\\" + txtRemoteComputer.Text + #"\root\cimv2");
System.Management.ManagementScope oMs = new System.Management.ManagementScope(path, conO);
oMs.Connect();
ObjectGetOptions opt = new ObjectGetOptions();
ManagementClass classInstance = new ManagementClass(oMs, path, opt);
ManagementBaseObject inParams = classInstance.GetMethodParameters("Create");
inParams["CommandLine"] = txtPath.Text;
ManagementBaseObject outParams = classInstance.InvokeMethod("Create", inParams, null);
lblInfo.Text = outParams["returnValue"].ToString() + " Process ID: {0}" + outParams["processId"].ToString();
However, I get the following error:
Operation is not valid due to the current state of the object.
Does anyone have a better solution for this problem?
Btw, I have the following scenario:
My client runs a server, in which he has some applications that do many different things (logging, calculations ...). He wants to be able to monitor and if needed to restart these applications from a web based client (even his mobile phone).
What I am trying to accomplish using this application is to kill a process and start the same one. Using System.Diagnostics I was able to query the processes that are currently running, but that obviously is not a solution, since for remote machines System.Diagnostics can only see processes, and not interact with them.
When you deal with WMI and get the error that you listed, generally it means that there was no data to return from your query. Looking at your code, it seems that you never make the call to where the process data is stored. You need to have a call to Win32_Process somewhere in your code. The code to get info on processes goes like this:
ManagementPath path = new ManagementPath(#"\\" + txtRemoteComputer.Text + #"\root\cimv2");
System.Management.ManagementScope oMs = new System.Management.ManagementScope(path, conO);
oMs.Connect();
ObjectQuery query = new ObjectQuery(
"SELECT * FROM Win32_Process");
ManagementObjectSearcher searcher =
new ManagementObjectSearcher(oMs, query);
foreach (ManagementObject queryObj in searcher.Get())
{
After that you do whatever with the code. Please note that this code is only to get the process, I have no experience with sending calls to end a process remotely, but at least with this you can start to check if the remote computer is evening running a certain process.

Categories