WMI call performance - c#

I need to make a WMI call in the constructor of my service. But when I start/restart the system this call takes significant amount of time.
I am using the following code to get the path of the windows service....
Here I've used the EnumerationOptions to improve the query performance, now in order to use it I have to use the ManagementScope which is "root\civm2", every time I've to use "root'civm2" as Management scope,
Earlier I was using managementObjectCollection.Count to know whether it contains any items or not, now to improve the performance I am using managementObjectEnumerator.MoveNext, will it help, I've commented the count related code.
Is there any better way to improve the performance of the same code...
EnumerationOptions options = new EnumerationOptions();
// options.Rewindable = false; **// I HAVE TO COMMENT OUT THIS IN ORDER TO GET THE RESULTS....**
options.ReturnImmediately = true;
string query = string.Format("SELECT PathName FROM Win32_Service WHERE Name = '{0}'", "MyService");
ManagementScope ms12 = new ManagementScope(#"root\cimv2");
ms12.Connect();
using (var managementObjectSearcher = new ManagementObjectSearcher(query))
{
managementObjectSearcher.Scope = ms12;
managementObjectSearcher.Options = options;
var managementObjectCollection = managementObjectSearcher.Get();
//if (managementObjectCollection.Count > 0)
//{
var managementObjectEnumerator = managementObjectCollection.GetEnumerator();
if (managementObjectEnumerator.MoveNext())
{
var invalidChars = new Regex(string.Format(CultureInfo.InvariantCulture, "[{0}]", Regex.Escape(new string(Path.GetInvalidPathChars()))));
var path = invalidChars.Replace(managementObjectEnumerator.Current.GetPropertyValue("PathName").ToString(), string.Empty);
Console.WriteLine(path);
}
//}
else
{
Console.WriteLine("Else part...");
}
}
Am I using the scope and EnumerationOption in correct way??
Please guide.

As the answer to your another question suggest you can build the object path of the class and use the ManagementObject directly to improve the performance , now if you want to check if the ManagementObject return an instance you can use the private property IsBound.
string ServicePath = string.Format("Win32_Service.Name=\"{0}\"", "MyService");
var WMiObject = new ManagementObject(ServicePath);
PropertyInfo PInfo = typeof(ManagementObject).GetProperty("IsBound", BindingFlags.NonPublic | BindingFlags.Instance);
if ((bool)PInfo.GetValue(WMiObject, null))
{
string PathName = (string)WMiObject.GetPropertyValue("PathName");
var invalidChars = new Regex(string.Format(CultureInfo.InvariantCulture, "[{0}]", Regex.Escape(new string(Path.GetInvalidPathChars()))));
var path = invalidChars.Replace(PathName, string.Empty);
Console.WriteLine(path);
}
else
{
Console.WriteLine("Else part...");
}

It appears that in more recent versions of the .NET framework the binding happens as late as possible. At least this was the case for me when I was testing the existence of a specific shared folder.
Here is an update to #RRUZ 's solution which uses a try-catch instead of reflecting the IsBound internal property.
var servicePath = string.Format("Win32_Service.Name=\"{0}\"", "MyService");
string pathName = null;
try
{
var wmiObject = new ManagementObject(servicePath);
pathName = (string)wmiObject.GetPropertyValue("PathName");
}
catch {}
if (pathName != null)
{
var invalidChars = new Regex(string.Format(CultureInfo.InvariantCulture, "[{0}]", Regex.Escape(new string(Path.GetInvalidPathChars()))));
var path = invalidChars.Replace(pathName, string.Empty);
Console.WriteLine(path);
}
else
{
Console.WriteLine("Else part...");
}

Related

Issues with ambiguous call

I'm having hard time figuring out what the problem is. I'm trying to make sort of process monitor which loads processes list, ID, username of owner,memory usage and description.. and this error is giving me really big headache.
private void Button1_Click(object sender, EventArgs e)
{
Process[] procList = Process.GetProcesses();
foreach (Process process in procList)
{
// get status
string status = (process.Responding == true ? "Responding" : "Not responding");
// get username and description
string query = "SELECT * FROM Win32_Process WHERE ProcessID = " + process.Id;
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
ManagementObjectCollection processList = searcher.Get();
dynamic response = new ExpandoObject();
response.Description = "";
response.Username = "Unknown";
foreach (ManagementObject obj in processList)
{
// get username
string[] argList = new string[] { string.Empty, string.Empty };
int returnValue = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList));
if (returnValue == 0)
response.Username = argList[0];
if (obj["ExecutablePath"] != null)
{
try
{
FileVersionInfo info = FileVersionInfo.GetVersionInfo(obj["ExecutablePath"].ToString());
response.Description = info.FileDescription;
}
catch { }
}
}
// get memory usage
int memsize = 0; // memsize in Megabyte
PerformanceCounter PC = new PerformanceCounter();
PC.CategoryName = "Process";
PC.CounterName = "Working Set - Private";
PC.InstanceName = process.ProcessName;
memsize = Convert.ToInt32(PC.NextValue()) / (int)(1024);
memsize = (memsize / 1024);
PC.Close();
PC.Dispose();
ListViewItem item = new ListViewItem();
item.Text = process.Id.ToString();
item.SubItems.Add(process.ProcessName);
item.SubItems.Add(status);
item.SubItems.Add(response.Username);
item.SubItems.Add(memsize.ToString() + " MB");
item.SubItems.Add(response.Description);
listView1.Items.Add(item);
}
}
When i try debugging the program, it outputs few of them without any problem, (see here -> https://i.imgur.com/D4ftBgb.png) and then error shows up -> https://i.imgur.com/m1R90hz.png
Because you use dynamic, method overload resolution is delayed until runtime. You have a null response.Username or response.Description, so the dynamic runtime doesn't know which overload to call. Compare:
public class Test
{
public static void Main()
{
dynamic bar = null;
try
{
Foo(bar);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private static void Foo(string f) { }
private static void Foo(int? o) { }
}
This throws the same exception, because both overloads can accept a null, and there is no further type information present.
To resolve this, either specify the overload explicitly by casting to string:
Foo((string)bar);
Or in your case, SubItems.Add((string)response.Username).
Or simply don't use dynamic to stuff your variables in, but keep them both declared as separate variables: string description = "", username = "".
The type of both your response.Username and response.Description is dynamic. The ListViewSubItemCollection.Add() can't decide which overload to use, therefore, you need to convert them to string.
Try the following:
string username = Convert.ToString(response.Username);
string description = Convert.ToString(response.Description);
ListViewItem item = new ListViewItem();
item.Text = process.Id.ToString();
item.SubItems.Add(process.ProcessName);
item.SubItems.Add(status);
item.SubItems.Add(username);
item.SubItems.Add(memsize.ToString() + " MB");
item.SubItems.Add(description);
listView1.Items.Add(item);
The best long term solution is to remove your use of dynamic and use an explicit class with Description and Username properties.
The most direct fix is to change:
response.Description = info.FileDescription;
to:
response.Description = info.FileDescription ?? "";
Why is that necessary (the ?? "")? It will allows the overload resolution to work correctly since Description will never be null. The reason why it doesn't work when null is that a null property of an ExpandoObject has no type associated with it. This is different to a normal class whereby the compiler knows that the type of the property is string.

Get "initial Key" value from .mp3 file

I can't find a way to read the "initial key" property from an mp3 file to use the song information in my application.
I've already tried to find libraries which do the job for me. I found TagLib# which is a very cool solution for getting tags/properties of different file formats. (including mp3).
I can use this library to get the title, the artist, the beats per minute and so on.. only the initial key value is missing for my use which isn't featured, unfortunately.
I also tried to find other solutions which support the initial key property but I haven't found one.
I already found a source which seems to address the same issue and solves it with using TagLib#, but I can't figure out how he solved that problem.
Use Ctrl + F and search for "Initial" to find the code block.
You can find the link here
I'll post a short part of my code which can be used to determine different info about one song in a pattern like this: (["bpm"]"title" - "artist")
var file = TagLib.File.Create(filePath);
return $"[{file.Tag.BeatsPerMinute}]{file.Tag.Title} - {file.Tag.FirstPerformer}";
Thanks for any help or recommendations in advance! :)
Try this:
public static void Main(string[] args)
{
var path = …
var file = TagLib.File.Create (path);
var id3tag = (TagLib.Id3v2.Tag)file.GetTag (TagTypes.Id3v2);
var key = ReadInitialKey (id3tag);
Console.WriteLine ("Key = " + key);
}
static string ReadInitialKey(TagLib.Id3v2.Tag id3tag)
{
var frame = id3tag.GetFrames<TextInformationFrame>().Where (f => f.FrameId == "TKEY").FirstOrDefault();
return frame.Text.FirstOrDefault() ;
}
On Windows 10 you can also use:
async Task<string> ReadInitialKey(string path)
{
StorageFile file = await StorageFile.GetFileFromPathAsync(path);
Windows.Storage.FileProperties.MusicProperties musicProperties = await file.Properties.GetMusicPropertiesAsync();
var props = await musicProperties.RetrievePropertiesAsync(null);
var inkp = props["System.Music.InitialKey"];
return (string)inkp;
}
See here for documentation on MusicProperties object and here for the valid music properties.
You can use the Shell to read all MP3 properties.
Test on Windows 10, VS 2015 =>
// Add Reference Shell32.DLL
string sFolder = "e:\\";
string sFile= "01. IMANY - Don't Be so Shy (Filatov & Karas Remix).mp3";
List<string> arrProperties = new List<string>();
Shell objShell = new Shell();
Folder objFolder;
objFolder = objShell.NameSpace(sFolder);
int nMaxProperties = 332;
for (int i = 0; i < nMaxProperties; i++)
{
string sHeader = objFolder.GetDetailsOf(null, i);
arrProperties.Add(sHeader);
}
FolderItem objFolderItem = objFolder.ParseName(sFile);
if (objFolderItem != null)
{
for (int i = 0; i < arrProperties.Count; i++)
{
Console.WriteLine((i + ('\t' + (arrProperties[i] + (": " + objFolder.GetDetailsOf(objFolderItem, i))))));
}
}
Just borrowing code from nuget: mono TaglibSharp:
var tfile = TagLib.File.Create(#"..");
string initialKey = null;
if (tfile.GetTag(TagTypes.Id3v2) is TagLib.Id3v2.Tag id3v2)
{
/*
// test: add custom Initial Key tag
var frame = TextInformationFrame.Get(id3v2, "TKEY", true);
frame.Text = new[] {"qMMM"};
frame.TextEncoding = StringType.UTF8;
tfile.Save();
*/
var frame = TextInformationFrame.Get(id3v2, "TKEY", false);
initialKey = frame?.ToString();
}

Using ManagementObjectSearcher to get an exact BitLocker WMI value

Good Day All,
I am having an issue with ManagementObjectSearcher. I am trying to query the exact value that i want but cannot find any reference to the precise syntax requirements and I continually receive an error when trying to finish out the code to be what I need it to be.
the specific portion of code that is presenting the issue is when I check for the drives Encryption state(I know for a fact that my disk is not encrypted on this machine, which is why that is the only value i have if'd currently). Any assistance in getting this code to pull the correct value would be greatly appreciated.
I've tried both the "=" method and the "LIKE" method with no change in output.
using Microsoft.Win32;
using System;
using System.Drawing;
using System.IO;
using System.Management;
using System.Windows.Forms;
public Form1()
{
InitializeComponent();
// Check for OS Version
string OSVer = Convert.ToString(Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "ProductName", null));
OSDialog.Text = OSVer;
// Check Architecture
if (Directory.Exists("C:\\Program Files (x86)"))
{
ArchitectureDialog.Text = "64 Bit";
}
else
{
ArchitectureDialog.Text = "32 Bit";
}
// Check Encryption
ManagementObjectSearcher Collect = new ManagementObjectSearcher("SELECT ProtectionStatus FROM Win32_EncryptableVolume WHERE DriveLetter = 'C:'");
string Encryption = Collect.ToString();
if (Encryption == "0")
{
EncryptionDialog.Text = "Disk is not Encrypted";
EncryptionDialog.ForeColor = Color.Green;
}
}
private void Cancel_Click(object sender, EventArgs e)
{
Close();
}
Getting BitLocker information from WMI requires elevated permissions. Your code has to be running as an admin and you have to ask for elevated privileges. So, I don't use ManagementObjectSearcher to obtain BitLocker info. Instead, I do something similar to the following (modified to your scenario - but not tested as shown):
ManagementObject GetBitLockerManager( string driveLetter )
{
var path = new ManagementPath( );
path.Server = string.Empty;
path.NamespacePath = #"\ROOT\CIMV2\Security\MicrosoftVolumeEncryption";
path.ClassName = "Win32_EncryptableVolume";
var options = new ConnectionOptions( );
options.Impersonation = ImpersonationLevel.Impersonate;
options.EnablePrivileges = true;
options.Authentication = AuthenticationLevel.PacketPrivacy;
var scope = new ManagementScope( path, options );
var mgmt = new ManagementClass( scope, path, new ObjectGetOptions( ) );
mgmt.Get( );
return mgmt
.GetInstances( )
.Cast<ManagementObject>( )
.FirstOrDefault
( vol =>
string.Compare
(
vol[ "DriveLetter" ] as string,
driveLetter,
true
) == 0
);
}
OK so I figured it out, thank you for all of the assistance provided. Code is below.
ManagementObjectSearcher Encryption = new ManagementObjectSearcher(#"root\cimv2\Security\MicrosoftVolumeEncryption", "SELECT * FROM Win32_EncryptableVolume");
foreach (ManagementObject QueryObj in Encryption.Get())
{
string EncryptionStatus = QueryObj.GetPropertyValue("ProtectionStatus").ToString();
if (EncryptionStatus == "0")
{
EncryptionDialog.Text = "Unencrypted";
}
else if (EncryptionStatus == "1")
{
EncryptionDialog.Text = "Encrypted - SysPrep will not complete";
}
else if (EncryptionStatus == "2")
{
EncryptionDialog.Text = "Cannot Determine Encryption";
}
}

How to get motherboard ID without receiving empty strings?

I tried many things:
//public static string GetMotherBoardID()
//{
// string mbInfo = String.Empty;
// //Get motherboard's serial number
// ManagementObjectSearcher mbs = new ManagementObjectSearcher("Select * From Win32_BaseBoard");
// foreach (ManagementObject mo in mbs.Get())
// mbInfo += mo["SerialNumber"].ToString();
// return mbInfo;
//}
//public static string GetMotherBoardID()
//{
// string mbInfo = String.Empty;
// ManagementScope scope = new ManagementScope("\\\\" + Environment.MachineName + "\\root\\cimv2");
// scope.Connect();
// ManagementObject wmiClass = new ManagementObject(scope, new ManagementPath("Win32_BaseBoard.Tag=\"Base Board\""), new ObjectGetOptions());
// foreach (PropertyData propData in wmiClass.Properties)
// {
// if (propData.Name == "SerialNumber")
// mbInfo = String.Format("{0,-25}{1}", propData.Name, Convert.ToString(propData.Value));
// }
// return mbInfo;
//}
public static string GetMotherBoardID()
{
string mbInfo = String.Empty;
ManagementObjectSearcher mbs = new ManagementObjectSearcher("Select * From Win32_BaseBoard");
ManagementObjectCollection moc = mbs.Get();
ManagementObjectCollection.ManagementObjectEnumerator itr = moc.GetEnumerator();
itr.MoveNext();
mbInfo = itr.Current.Properties["SerialNumber"].Value.ToString();
var enumerator = itr.Current.Properties.GetEnumerator();
if (string.IsNullOrEmpty(mbInfo))
mbInfo = "0";
return mbInfo;
}
This all gives empty string on my PC, but the correct ID on the laptop.
Some other person also reporting on two PCs is empty motherboard ID.
The result of:
public static string GetMotherBoardID()
{
string mbInfo = String.Empty;
ManagementObjectSearcher mbs = new ManagementObjectSearcher("Select * From Win32_BaseBoard");
ManagementObjectCollection moc = mbs.Get();
ManagementObjectCollection.ManagementObjectEnumerator itr = moc.GetEnumerator();
itr.MoveNext();
mbInfo = itr.Current.Properties["SerialNumber"].Value.ToString();
var enumerator = itr.Current.Properties.GetEnumerator();
string properties = "";
while (enumerator.MoveNext())
{
properties += "[" + enumerator.Current.Name + "][" + (enumerator.Current.Value != null ? enumerator.Current.Value.ToString() : "NULL") + "]\n";
}
if (string.IsNullOrEmpty(mbInfo))
mbInfo = "0";
return mbInfo;
}
[Caption][Основная плата]
[ConfigOptions][NULL]
[CreationClassName][Win32_BaseBoard]
[Depth][NULL]
[Description][Основная плата]
[Height][NULL]
[HostingBoard][True]
[HotSwappable][False]
[InstallDate][NULL]
[Manufacturer][Gigabyte Technology Co., Ltd.]
[Model][NULL]
[Name][Основная плата]
[OtherIdentifyingInfo][NULL]
[PartNumber][NULL]
[PoweredOn][True]
[Product][H55M-S2H]
[Removable][False]
[Replaceable][True]
[RequirementsDescription][NULL]
[RequiresDaughterBoard][False]
[SerialNumber][ ]
[SKU][NULL]
[SlotLayout][NULL]
[SpecialRequirements][NULL]
[Status][OK]
[Tag][Base Board]
[Version][x.x]
[Weight][NULL]
[Width][NULL]
Maybe c# is bad for retrieving such things?
I hope for solution on C/C++ or working solution on C#
Some motherboards simply don't have ID. It set to empty string.
So, if someone need to use motherboard unique thing for licensing purposes they should receive motherboard UUID.
Personally, I'd recommend using this particular Open Source hardware monitor library (you'll need the source). You can use it for hardware identification. Open Hardware Monitor
There is also a NuGet package called DeviceID. However, you will need to include their DLL with your package, but is a great fast, simple solution.
Here a usage example:
/* Depends on https://www.nuget.org/packages/DeviceId/ Install-Package DeviceId - Version 5.2.0*/
/* Using AddMacAddress(true, true) to exclude both virtual and wireless network adapters. */
readonly string MachineSupportID = new DeviceIdBuilder()
.AddMacAddress(true, true)
.AddMotherboardSerialNumber()
.AddProcessorId()
.AddSystemDriveSerialNumber()
.ToString();
May the force be with you.

Create a new Hyper-V VM (using WMI) with specific hardware

I'm wanting to create a new Hyper-V VM with a determined amount of RAM, network card, number of processor cores, and attach a VHD file to the IDE controller.
The problem is that the Msvm_ResourceAllocationSettingData is not very easy to work with. The code I'm using doesn't work (this is code to attach a VHD to an existing VM, however I would also like to use it when creating a new VHD too).
public void AttachVhd(IdeChannel ideChannel, String vhdPath) {
// Get VirtualSystemSettingData
ManagementObject vssd = this.GetVirtualSystemSettingData();
// Get the IDE Controller
ManagementObject ideController = this.GetResourceAllocationSettingData(ResourceType.IdeController, ResourceSubType.IdeController);
// Create synthetic disk:
ManagementObject syntheticDiskRasd = this.GetResourceAllocationSettingData(ResourceType.Disk, ResourceSubType.DiskSynthetic);
syntheticDiskRasd["Parent"] = ideController.Path;
syntheticDiskRasd["Address"] = ideChannel == IdeChannel.Primary ? "0" : "1";
this.AddVirtualSystemResources(syntheticDiskRasd);
// Attach it
ManagementObject vhdRasd = this.GetResourceAllocationSettingData(ResourceType.StorageExtent, ResourceSubType.Vhd); ;
vhdRasd["Parent"] = syntheticDiskRasd.Path;
vhdRasd["Connection"] = new String[] { vhdPath };
this.AddVirtualSystemResources( vhdRasd );
// Cleanup
vhdRasd.Dispose();
syntheticDiskRasd.Dispose();
ideController.Dispose();
vssd.Dispose();
}
private ManagementObject GetResourceAllocationSettingData(ResourceType resourceType, ResourceSubType resourceSubType)
{
String desiredSubType = ResourceSubTypeStrings.GetString(resourceSubType); // Scout.Common.Extensions.GetDescription( resourceType );
using(ManagementObjectCollection settingsDatas = _vm.GetRelated("Msvm_VirtualSystemSettingData"))
foreach(ManagementObject settingData in settingsDatas)
{
using(ManagementObjectCollection rasds = settingData.GetRelated("Msvm_ResourceAllocationSettingData"))
foreach(ManagementObject rasd in rasds)
{
ResourceType rasdResourceType = (ResourceType)(UInt16)rasd["ResourceType"];
String rasdResourceSubType = (String)rasd["ResourceSubType"];
String rasdOtherType = (String)rasd["OtherResourceType"];
if( rasdResourceType == resourceType && rasdResourceSubType == desiredSubType )
{
return rasd;
}
}
}
return null;
}
private void AddVirtualSystemResources(ManagementObject rasd)
{
using (ManagementObject vmService = HyperV.GetManagementService())
{
ManagementBaseObject inParams = vmService.GetMethodParameters("AddVirtualSystemResources");
inParams["TargetSystem"] = _vm;
inParams["ResourceSettingsData"] = rasd.GetText(TextFormat.CimDtd20);
ManagementBaseObject outParams = vmService.InvokeMethod("AddVirtualSystemResources", inParams, options: null);
String[] addedResources = (String[])outParams["NewResources"];
OperationReturnCode returnValue = (OperationReturnCode)(UInt32)outParams["ReturnValue"];
if (returnValue == OperationReturnCode.JobStarted)
{
String jobPath = (String)outParams["Job"];
HyperV.MonitorJob(jobPath);
}
else if (returnValue == OperationReturnCode.Completed)
{
}
else
{
throw new ApplicationException( returnValue.ToString() );
}
}
}
Rather than find your problem, can I point you to an example that works?
See WmiCalls.DeployVirtualMachine in my Apache CloudStack Hyper-V plugin
Post a comment if you need additional detail, and I will update the answer.

Categories