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.
Related
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");
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 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
/
I am currently developing an application in C# (.NET 4.0) that should have as a part of its functionality the ability to determine the percentage of fragmentation on a particular volume. All the other features have been tested and are working fine but I’ve hit a snag trying to access this data. I would ideally prefer to use WMI as this matches the format I’m using for the other features but at this point I’m willing to use anything that can be efficiently integrated into the application, even if I have to use RegEx to filter the data. I am currently doing the development on a Windows 7 Professional (x64) machine. I have tested the following Powershell snippet using Administrator rights and it works flawlessly.
$drive = Get-WmiObject -Class Win32_Volume -Namespace root\CIMV2 -ComputerName . | Where-Object { $_.DriveLetter -eq 'D:' }
$drive.DefragAnalysis().DefragAnalysis
This is the method I’m using in C# to accomplish the same thing, but the InvokeMethod keeps returning 11 (0xB).
public static Fragmentation GetVolumeFragmentationAnalysis(string drive)
{
//Fragmenation object initialization removed for simplicity
try
{
ConnectionOptions mgmtConnOptions = new ConnectionOptions { EnablePrivileges = true };
ManagementScope scope = new ManagementScope(new ManagementPath(string.Format(#"\\{0}\root\CIMV2", Environment.MachineName)), mgmtConnOptions);
ObjectQuery query = new ObjectQuery(string.Format(#"SELECT * FROM Win32_Volume WHERE Name = '{0}\\'", drive));
scope.Connect();
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query))
{
object[] outputArgs = new object[2];
foreach (ManagementObject moVolume in searcher.Get())
{
// Execution stops at this line as the result is always 11
UInt32 result = (UInt32)moVolume.InvokeMethod("DefragAnalysis", outputArgs);
if (result == 0)
{
Console.WriteLine("Defrag Needed: = {0}\n", outputArgs[0]);
ManagementBaseObject mboDefragAnalysis = outputArgs[1] as ManagementBaseObject;
if (null != mboDefragAnalysis)
{
Console.WriteLine(mboDefragAnalysis["TotalPercentFragmentation"].ToString());
}
}
else
{
Console.WriteLine("Return Code: = {0}", result);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Could not acquire fragmentation data.\n" + ex);
}
return result;
}
I have even added the following line to the app.manifest but still nothing.
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
Could somebody please tell me what I’m overlooking? Failure is not an option for me on this, so if it cannot be done using C# I don’t mind creating a DLL in another language (even if I have to learn it), that will give me the results I need. Ideally the application should be able work on any OS from XP upwards and must be totally transparent to the user.
These are the resources I have already used. I wanted to add the jeffrey_wall blog on msdn as well but as a new user I can only add 2 hyperlinks at a time. Thanks again.
http://www.codeproject.com/Messages/2901324/Re-the-result-of-DefragAnalysis-method-in-csharp.aspx
http://social.technet.microsoft.com/Forums/vi-VN/winserverfiles/thread/9d56bfad-dcf5-4258-90cf-4ba9247200da
Try building your application targeting 'Any CPU' - on the Build tab of the project properties. I suspect you're using a target of x86. I get the same error code on my Win7 x64 machine if I do that.
In fact, running your PowerShell snippet in the x86 version of PowerShell gives an empty set of results, too.
You get the same error if you run either piece of code without full Administrator privileges, as you've found, so also ensure your app.manifest is correct. A UAC prompt is a handy hint that it's taking effect!
No idea why this WMI query doesn't like running under WoW64, I'm afraid, but hopefully this will give you a head-start.
You could simply call the PowerShell command you mentioned in your post, since you said the PowerShell code works. From C# you would want to follow this workflow:
Instantiate a PowerShell RunSpace
Open the RunSpace
Add a script to the Commands property
Invoke the command list
Here is an example of how to achieve this, and process the resulting object output.
http://www.codeproject.com/Articles/18229/How-to-run-PowerShell-scripts-from-C
For Windows XP and Windows Vista, you would have to ensure that PowerShell was installed on each of the systems you want to run your program on. Not a bad prerequisite to have, but something to keep in mind as a dependency.
Hope this helps.
The 32-bit WMI provider for Win32_Volume doesn't seem to be able to start the defragsvc for whatever reason. You can force the 64-bit WMI provider even in a 32-bit client running under WOW64 by changing your code to add an additional WMI connection option:
ConnectionOptions mgmtConnOptions = new ConnectionOptions {
EnablePrivileges = true,
Context = new ManagementNamedValueCollection() {
{ "__ProviderArchitecture", 64 }
}
};
I'm creating a watch dog service that will be monitoring other services on various remote servers (all in the same domain). The user that I'm using to connect to the remote servers is not an admin. When I try to enumerate the services in the Win32_Service class, I get an access denied error.
I've given the user 'Remote Enable' & 'Enable Account' persmissions to the Root\CIMV2 namespace in the WMI Control.
I am able to connect to the server with the following code. The object ServiceListItem is just a simple class that contains the server name and the service name:
SecureString secureString = new SecureString();
foreach ( char c in "password" )
{
secureString.AppendChar( c );
}
ConnectionOptions connectionOptions = new ConnectionOptions();
connectionOptions.Username = "domain\\user";
connectionOptions.SecurePassword = secureString;
foreach ( ServiceListItem service in _serviceList )
{
ManagementScope managementScope = new ManagementScope();
managementScope = new ManagementScope( String.Format( #"\\{0}\root\cimv2", service.ServerName ), connectionOptions );
managementScope.Connect();
//RelatedObjectQuery relatedObjectQuery = new RelatedObjectQuery( String.Format( "Win32_Service.Name='{0}'", service.ServiceName ) );
//ManagementObjectSearcher objectSearcher = new ManagementObjectSearcher( managementScope, relatedObjectQuery );
ObjectQuery objectQuery = new ObjectQuery( "SELECT * FROM Win32_Service WHERE Name = '" + service.ServiceName + "'" );
ManagementObjectSearcher objectSearcher = new ManagementObjectSearcher( managementScope, objectQuery );
ManagementObjectCollection objectCollection = objectSearcher.Get();
foreach ( ManagementObject managementObject in objectCollection )
{
serviceStatus = managementObject.Properties["State"].Value.ToString();
Debug.Print(service.ServiceName + " - " + serviceStatus);
//break;
}
}
The managementScope.Connect() runs fine, which means the wmi security on cimv2 is set up correctly. However, when I try to enumerate the objectCollection, I get the 'Access Denied' exception. This tells me (I think) that the user doesn't have permissions to enumerate the Win32_Service class (SC_MANAGER_ENUMERATE_SERVICE).
I just haven't been able to find any good examples on how to enable that permission for a remote user. I'm not very experienced when it comes to coding with Windows api's, so please be as detailed as possible in your answers :)
Trying to find the same answer myself today, I've been doing a lot of googling. After a good half hour of incantations, I found this MSDN article (907460) which uses sc sdet. It seems to work so far, even though the security descriptor is for Windows Server 2003. I've found you can do sc sdshow SCMANAGER to get the current value so when back in the office tomorrow I'll be comparing an contrasting to make sure I've not locked something out I shouldn't have :-)
For completeness, the notes in KB907460 (in case it moves/goes away):
Symptoms: After you install Microsoft Windows Server 2003 Service Pack 1 (SP1), non-administrators cannot remotely access the Service Control Manager.
Cause: Windows Server 2003 SP1 changes the Service Control Manager default security settings.
Resolution:
To resolve this issue, use version 5.2.3790.1830 of the Sc.exe tool.
This tool is located in the %windir%\System32 folder. To do this,
follow these steps:
Click Start, click Run, type cmd, and then click OK.
Type the following command at the command prompt, and then press ENTER:
sc sdset SCMANAGER D:(A;;CCLCRPRC;;;AU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)S:(AU;FA;KA;;;WD)(AU;OIIOFA;GA;;;WD)
I found myself stuck into a similar problem. In my case it had nothing to do with permissions, which I did set by following this link: http://www.poweradmin.com/help/enableWMI.aspx
So, After hours of wondering lost I found this article that tells how UAC interfere with your set of permissions and how can you fix that:
http://www.solarwinds.com/documentation/apm/docs/APMWMITroubleshooting.pdf
In my case, the registry key didn't existed, so I created it.
Hope this helps also, cheers!