Windows service. Edit the StartName - c#

I need to check and edit the windows service's StartName.
I have founded this solution throught the WMI API:
using (var service = new ManagementObject($"Win32_Service.Name = '{my_name}'"))
{
using (var inParams = service.GetMethodParameters("Change"))
{
inParams["StartName"] = value;
ManagementBaseObject outParams = service.InvokeMethod("Change", inParams, null);
outParams.Dispose();
}
}
When I invoke this code by a new console project application with the Visual Studio that had been lauched as administrator it works fine.
But when I invoke it by the windows service that had loged as LocalSystem i got an exception:
System.Management.ManagementException: Provider Load Failur. in
System.Management.ManagementException.ThrowWithExtendedInfo(ManagementStatus
errorCode) in System.Management.ManagementObject.InvokeMethod(String
methodName, ManagementBaseObject inParameters, InvokeMethodOptions
options) ...
What have I missed in this case?
What permissions/application state or whatever do I must to change to edit the service StartName by the windows service?

I think that the better approach would be change the name in te registry. You will probably have to start Visual Studio as Administrator. Link: https://stackoverflow.com/a/3887455/637840
The Change() method on WMI got a lot of overhead you don´t need. You will probably make a mistake on some parameter and potentially your service register is going to be broken. Take a look at everything you need to set in order to just change the name by WMI: https://learn.microsoft.com/es-es/windows/desktop/TermServ/win32-terminalservice-change
Anyway, if you need to do it by all means using WMI. You can use ORMi. You have a tutorial on the repo that will show you how to do method working.
Also you can use this as reference: https://medium.com/#luque.nicolas/compare-ormi-and-traditional-net-wmi-implementation-f00db26d10a3

Related

Getting strange error code when programmatically adding a machine to a domain using C#

I am trying to add a Windows machine (server 2008 R2) to a domain programmatically using C#. I know I have the correct permissions to add the machine to the domain because I am able to add it manually through the windows UI. I also know that my ManagementScope is correct because when I create it I am able to query any WMI object that I want. I am trying to connect as follows:
ManagementClass computerSystem = new ManagementClass(scope, new ManagementPath("Win32_ComputerSystem"), new ObjectGetOptions());
ManagementObjectCollection computerSystemInstances = computerSystem.GetInstances();
ManagementObject baseObject = computerSystemInstances.ToList<ManagementObject>().First();
ManagementBaseObject inParams = baseObject.GetMethodParameters("JoinDomainOrWorkgroup");
inParams["Name"] = "my.domain.com";
inParams["Password"] = domainCredentials.FullUserName;
inParams["UserName"] = domainCredentials.Password;
inParams["FJoinOptions"] = 1;
var joinParams = baseObject.InvokeMethod("JoinDomainOrWorkgroup", inParams, null);
The method invoke does not throw any exceptions, but the error code value found at joinParams.Properties["ReturnValue"].Value is 1312. I can't find any documentation anywhere (even on Microsoft's MSDN page for the method) stating what this error code means. Does anyone know where to find what this error code is for?
In your code, you've transposed your username and password to the wrong variables. This may be the cause?
This might be a system error code, found http://msdn.microsoft.com/en-us/library/ms681385%28v=vs.85%29
ERROR_NO_SUCH_LOGON_SESSION
1312 (0x520)
A specified logon session does not exist. It may already have been terminated.

How to obtain DefragAnalysis using C#

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 }
}
};

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.

Changing how a service is configured

When installing a service, there is a helpful .NET class called ServiceProcessInstaller. This class has a property Account, which is a ServiceAccount enumeration with possible values LocalService, LocalSystem, NetworkService and User.
This is fine at install-time, but does anybody know how I can change this value for an existing service?
I assuming that I need to move away from the actual install-type classes, and have been researching hooking into the advapi32 ChangeServiceConfig method, WMI and ManagementObjects etc.
Indeed I have found code which will actually change the account under which the service runs,
ManagementObject mo = new ManagementObject("Win32_Service.Name='" + myService + "'");
object[] configParams = new object[11];
configParams[6] = userName;
configParams[7] = password;
object result = mo.InvokeMethod("Change", configParams);
(which on its own looks a bit like black magic but makes sense when viewed with the ChangeServiceConfig signature)
However when I apply this code to a service which happens to be installed as LocalSystem, it has no effect (although when I interpret result the call is reporting success). This doesn't really surprise me since I am only setting a username and password, I am not saying "rather than running as a local service, this service needs to run under a specific user account".
Now, my gut feel is that I am heading along the right lines here. The problem is that none of the parameters in ChangeServiceConfig appear to offer the opportunity to do this.
Any ideas? TIA, Pete
Error code 16 means "Service marked for deletion". Sometimes when you change service parameter, in particular when you delete / re-create a service you need to reboot your PC for operation to complete. While it's still pending, you can't manipulate service and you get error code 16.
Also, it might not be the case, that you problem has something to do with the fact that the call is inside a dll. If you put you code in a test rig dll and call it from a test rig exe (the same way you tested it in a test rig exe) and don't create / delete service in between I think it will work anyway.
The reason it does not working in your application on my opinion has to do with what you did with the service before (and this something most likely is not described in your question).
You need Impersonate an thread to run at context of user.
Try this class :
A small C# Class for impersonating a User
or this one :
Impersonate User
Return code is 21: "Invalid Parameter".
I ran into the same issue: Problem occurs when trying to apply a new user/password to a service which currently has "LocalSystem" with "Allow Service to interact with desktop" enabled.
To resolve, set the "DesktopInteract" flag in the "Change" query
var query = new ManagementPath(string.Format("Win32_Service.Name='{0}'", serviceName)); // string.Format("SELECT * FROM Win32_Service where Name='{0}'", serviceName);
using (ManagementObject service = new ManagementObject(query))
{
object[] wmiParams = new object[10];
//WMI update doesn't work if the service's user is currently set to LocalSystem
// with Interact with desktop on
wmiParams[5] = false;
wmiParams[6] = serviceUserName;
wmiParams[7] = password;
//update credentials for the service
var rtn = service.InvokeMethod("Change", wmiParams);
}

How to set "interact with desktop" in windows service installer

I have a windows service which runs under system account and executes some programs from time to time (yeah,yeah, I know that's a bad practice, but that's not my decision). I need to set the "interact with desktop" check, to see the gui of that executed programs, after the service is installed. I've tried several ways, putting the code below in AfterInstall or OnCommited event handlers of my service installer:
ConnectionOptions coOptions = new ConnectionOptions();
coOptions.Impersonation = ImpersonationLevel.Impersonate;
ManagementScope mgmtScope = new System.Management.ManagementScope(#"root\CIMV2", coOptions);
mgmtScope.Connect();
ManagementObject wmiService = new ManagementObject("Win32_Service.Name='" + ServiceMonitorInstaller.ServiceName + "'");
ManagementBaseObject InParam = wmiService.GetMethodParameters("Change");
InParam["DesktopInteract"] = true;
ManagementBaseObject OutParam = wmiService.InvokeMethod("Change", InParam, null);
or
RegistryKey ckey = Registry.LocalMachine.OpenSubKey(
#"SYSTEM\CurrentControlSet\Services\WindowsService1", true);
if(ckey != null)
{
if(ckey.GetValue("Type") != null)
{
ckey.SetValue("Type", ((int)ckey.GetValue("Type") | 256));
}
}
both of these methods "work". They set the check, but after I start the service it launches the exe - and gui isn't shown! So, if I stop the service, recheck and start it again - bingo! everything starts and is shown. The second way to achieve the result is to reboot - after it the gui is also shown.
So the question is: Is there a correct way to set "interact with desktop" check, so it'll start working without rechecks and reboots?
OS: Windows XP (haven't tried Vista and 7 yet...)
private static void SetInterActWithDeskTop()
{
var service = new System.Management.ManagementObject(
String.Format("WIN32_Service.Name='{0}'", "YourServiceName"));
try
{
var paramList = new object[11];
paramList[5] = true;
service.InvokeMethod("Change", paramList);
}
finally
{
service.Dispose();
}
}
And finally after searching the internet for a week - I've found a great working solution:
http://asprosys.blogspot.com/2009/03/allow-service-to-interact-with-desktop.html
Find the desktop to launch into. This
may seem facetious but it isn't as
simple as it seems. With Terminal
Services and Fast User Switching there
can be multiple interactive users
logged on to the computer at the same
time. If you want the user that is
currently sitting at the physical
console then you're in luck, the
Terminal Services API call
WTSGetActiveConsoleSessionId will get
you the session ID you need. If your
needs are more complex (i.e. you need
to interact with a specific user on a
TS server or you need the name of the
window station in a non-interactive
session) you'll need to enumerate the
Terminal Server sessions with
WTSEnumerateSessions and check the
session for the information you need
with WTSGetSessionInformation.
Now you know what session you need to
interact with and you have its ID.
This is the key to the whole process,
using WTSQueryUserToken and the
session ID you can now retrieve the
token of the user logged on to the
target session. This completely
mitigates the security problem of the
'interact with the desktop' setting,
the launched process will not be
running with the LOCAL SYSTEM
credentials but with the same
credentials as the user that is
already logged on to that session! No
privilege elevation.
Using CreateProcessAsUser and the
token we have retrieved we can launch
the process in the normal way and it
will run in the target session with
the target user's credentials. There
are a couple of caveats, both
lpCurrentDirectory and lpEnvironment
must point to valid values - the
normal default resolution methods for
these parameters don't work for
cross-session launching. You can use
CreateEnvironmentBlock to create a
default environment block for the
target user.
There is source code of the working project attached.
Same as Heisa but with WMI. (code is Powershell, but can be easily ported to C#)
if ($svc = gwmi win32_service|?{$_.name -eq $svcname})
{
try {
$null = $svc.change($svc.displayname,$svc.pathname,16,1,`
"Manual",$false,$svc.startname,$null,$null,$null,$null)
write-host "Change made"
catch { throw "Error: $_" }
} else
{ throw "Service $svcname not installed" }
See MSDN: Service Change() method for param description.

Categories