{"id":746,"date":"2022-08-16T11:59:30","date_gmt":"2022-08-16T18:59:30","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/powershell-community\/?p=746"},"modified":"2022-08-16T13:03:46","modified_gmt":"2022-08-16T20:03:46","slug":"the-many-flavours-of-wmi-management","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/powershell-community\/the-many-flavours-of-wmi-management\/","title":{"rendered":"The many flavours of WMI management"},"content":{"rendered":"<p>WMI is arguably one of the greatest tools a system administrator can have. You can manage Windows workstations, interact with Microsoft products, like the Configuration Manager, monitor server&#8217;s resources and many more. Today, we are going to look at the different ways we can use WMI with PowerShell. Hopefully, at the end, you will not have a favorite, but know what to use for each occasion.<\/p>\n<h2>The Ways<\/h2>\n<p>There are three tools for managing WMI I want to share with you.<\/p>\n<ul>\n<li>The <strong>System.Management<\/strong> namespace.<\/li>\n<li>The WMI Scripting API.<\/li>\n<li>The CIM cmdlets.<\/li>\n<\/ul>\n<p>Wait, what about the WMI Cmdlets, like <code>Get-WmiObject<\/code>? There are two reasons we are not covering these today. These commands are only available for Windows PowerShell, and you will come to learn that the <strong>System.Management<\/strong> namespace is very similar. If you are still resisting trying PowerShell 7, I could not recommend it enough.<\/p>\n<h2>The Procedure<\/h2>\n<p>I want to cover tasks we face everyday while administering Windows devices. We will look at:<\/p>\n<ul>\n<li>Querying.<\/li>\n<li>Calling a WMI Class method.<\/li>\n<li>Creating, Updating and Deleting a WMI Class Instance.<\/li>\n<li>Bonus: Creating, Populating and Deleting a custom WMI Class.<\/li>\n<\/ul>\n<p>I also want to show the pros and cons of each method, and where one stands out from the others.<\/p>\n<h2>The System.Management Namespace<\/h2>\n<p>If I had to pick a favorite, it would be this one. Bringing an object-oriented &#8220;feel&#8221; to WMI, this .NET namespace makes WMI management intuitive. Plus, if you are a C# developer, this will feel like home.<\/p>\n<h3>Querying<\/h3>\n<p>To perform a query, we need an instance of the <strong>ManagementObjectSearcher<\/strong> class. There are three constructors worth looking at:<\/p>\n<ul>\n<li><code>ManagementObjectSearcher(String)<\/code> \n<ul>\n<li>The simplest one. Creates a searcher object specifying the query string.<\/li>\n<\/ul>\n<\/li>\n<li><code>ManagementObjectSearcher(String, String)<\/code> \n<ul>\n<li>Creates the object with the query and the scope.<\/li>\n<\/ul>\n<\/li>\n<li><code>ManagementObjectSearcher(ManagementScope, ObjectQuery)<\/code> \n<ul>\n<li>The same as the previous one, but with instances of the objects instead of strings. This gives you more options.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Once we have the searcher, we call the <strong>Get<\/strong> method to return the <strong>ManagementObjects<\/strong>.<\/p>\n<pre><code class=\"language-powershell\">$query = \"Select * From Win32_Process Where Name = 'powershell.exe'\"\n$searcher = [wmisearcher]($query)\n$result = $searcher.Get()<\/code><\/pre>\n<p>The <code>$result<\/code> variable holds an instance of the <strong>ManagementObjectCollection<\/strong> class. This collection contains all the <strong>Win32_Process<\/strong> instances in the form of <strong>ManagementObjects<\/strong>.<\/p>\n<pre><code class=\"language-powershell\">$result = $searcher.Get()\n$result | Format-Table -Property ProcessId, Name, ExecutablePath -AutoSize\n\n```Output\nProcessId Name           ExecutablePath\n--------- ----           --------------\n     4116 powershell.exe C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe<\/code><\/pre>\n<p>This is how it looks like using the second and third constructors.<\/p>\n<pre><code class=\"language-powershell\">$query = \"Select * From Win32_Process Where Name = 'powershell.exe'\"\n$scope = 'root\\cimv2'\n$searcher = [wmisearcher]::new($scope, $query)\n$result = $searcher.Get()\n\n# Or\n\n$query = [System.Management.ObjectQuery]::new(\"Select * From Win32_Process Where Name = 'powershell.exe'\")\n$scope = [System.Management.ManagementScope]::new('root\\cimv2')\n$scope.Connect()\n$searcher = [System.Management.ManagementObjectSearcher]::new($scope, $query)\n$result = $searcher.Get()<\/code><\/pre>\n<h3>Calling a WMI Method<\/h3>\n<p>We could either call a method on the <strong>ManagementObject<\/strong> resultant from our query operation, like <strong>Terminate<\/strong>, or call a method on the WMI Class object. Let&#8217;s create a new process using the <strong>Create<\/strong> method.<\/p>\n<pre><code class=\"language-powershell\">$commandLine = 'powershell.exe -ExecutionPolicy Bypass -Command \"Write-Output ''Howdy! From WMI!''; Read-Host\"'\n$processClass = [wmiclass]'Win32_Process'\n# The parameters are: CommandLine, CurrentDirectory and ProcessStartupInformation.\n$processClass.Create($commandLine, $null, $null)<\/code><\/pre>\n<p>If the method succeeds, you should be presented with a PowerShell console, and the <strong>Output Parameters<\/strong>:<\/p>\n<pre><code class=\"language-Output\">__GENUS          : 2\n__CLASS          : __PARAMETERS\n__SUPERCLASS     :\n__DYNASTY        : __PARAMETERS\n__RELPATH        :\n__PROPERTY_COUNT : 2\n__PROPERTY_COUNT : 2\n__DERIVATION     : {}\n__SERVER         :\n__NAMESPACE      :\n__PATH           :\nProcessId        : 11896\nReturnValue      : 0\nPSComputerName   :<\/code><\/pre>\n<h3>Creating, Updating and Deleting a WMI Class Instance<\/h3>\n<p>We are going to use the <code>ManagementClass.CreateInstance()<\/code> method to create a new instance of the <strong>SMS_Collection<\/strong> class and <strong>Put<\/strong> to save it to the namespace.<\/p>\n<pre><code class=\"language-powershell\">$collection = ([wmiclass]'root\\SMS\\site_PS1:SMS_Collection').CreateInstance()\n$collection.Name = 'AwesomeDeviceCollection'\n$collection.LimitingCollectionID = 'PS1000042'\n$collection.Put()\n# The Get() method updates the $collection object with the new\n# property values populated by the Config Manager.\n$collection.Get()<\/code><\/pre>\n<p>Updating and deleting.<\/p>\n<pre><code class=\"language-powershell\">$collection = [wmiclass]\"root\\SMS\\site_PS1:SMS_Collection.CollectionID='PS1000043'\"\n$collection.Name = 'AwesomeDeviceCollection_NewName'\n$collection.Put()\n\n# Deleting\n\n$collection.Delete()<\/code><\/pre>\n<h2>The WMI Scripting API<\/h2>\n<p>The WMI Scripting API is nothing more than the exposure of the WMI COM interfaces through a Runtime Callable Wrapper. Or WMI COM Object, for short. This method of managing WMI is not as straight forward as the <strong>System.Management<\/strong> namespace, but its implementation gives great flexibility.<\/p>\n<h3>Querying<\/h3>\n<p>To start, we need to instantiate a <strong>SWbemLocator<\/strong> object, which will be the interface to the other objects, and obtain a <strong>SWbemServices<\/strong> object, by connecting to the server.<\/p>\n<pre><code class=\"language-powershell\">$locator = New-Object -ComObject 'WbemScripting.SWbemLocator'\n$services = $locator.ConnectServer()<\/code><\/pre>\n<p>Then, we use the <strong>ExecQuery<\/strong> method, from the <strong>SWbemServices<\/strong> object to perform our query. This method returns a <strong>SWbemObjectSet<\/strong>, which is a collection of <strong>SWbemObjects<\/strong>. Its properties are under the <strong>Properties_<\/strong> property.<\/p>\n<pre><code class=\"language-powershell\">$result = $services.ExecQuery(\"Select * From Win32_Process Where Name = 'powershell.exe'\")\n$object = $result | Select-Object -First 1\n$value = $object.Properties_['ProcessId'].Value<\/code><\/pre>\n<h3>Calling a WMI Method<\/h3>\n<p>First, we need to create an instance of the <strong>__Properties<\/strong> class, which holds the input parameters for the <strong>Create<\/strong> method. Then, we use the <code>SWbemServices.ExecMethod()<\/code> method to call <strong>Create<\/strong>.<\/p>\n<pre><code class=\"language-powershell\">$commandLine = 'powershell.exe -ExecutionPolicy Bypass -Command \"Write-Output ''Howdy! From WMI!''; Read-Host\"'\n$parameters = $object.Methods_['Create'].InParameters.SpawnInstance_()\n$parameters.Properties_['CommandLine'].Value = $commandLine\n\n$output = $services.ExecMethod('Win32_Process', 'Create', $parameters)<\/code><\/pre>\n<p>The <code>$output<\/code> variable contains a <strong>SWbemObject<\/strong>, which is an instance of the <strong>Output Parameters<\/strong> property class.<\/p>\n<pre><code class=\"language-powershell\">$services.ExecMethod('Win32_Process', 'Create', $parameters)<\/code><\/pre>\n<pre><code class=\"language-Output\">Value       : 16172\nName        : ProcessId\nIsLocal     : True\nOrigin      : __PARAMETERS\nCIMType     : 19\nQualifiers_ : System.__ComObject\nIsArray     : False\n\nValue       : 0\nName        : ReturnValue\nIsLocal     : True\nOrigin      : __PARAMETERS\nCIMType     : 19\nQualifiers_ : System.__ComObject\nIsArray     : False<\/code><\/pre>\n<h3>Creating, Updating and Deleting a WMI Class Instance<\/h3>\n<p>Let&#8217;s replicate our last example using the Scripting API.<\/p>\n<pre><code class=\"language-powershell\">$collection = $services.Get('\\\\.\\root\\SMS\\site_PS1:SMS_Collection').SpawnInstance_()\n$collection.Properties_['Name'].Value = 'AwesomeDeviceCollection'\n$collection.Properties_['LimitingCollectionID'].Value = 'PS1000042'\n$collection.Put_()<\/code><\/pre>\n<p>Updating and deleting.<\/p>\n<pre><code class=\"language-powershell\">$collection = $services.Get(\"\\\\.\\root\\SMS\\site_PS1:SMS_Collection.CollectionID='PS1000043'\")\n$collection.Properties_['Name'].Value = 'AwesomeDeviceCollection_NewName'\n$collection.Put_()\n\n# Deleting\n\n$collection.Delete_()<\/code><\/pre>\n<h2>The CIM Cmdlets<\/h2>\n<p>When you just want to perform a WMI query to analyze data, and not necessarily interact with it, you cannot beat the CIM Cmdlets. They are extremely fast, and provide unique tools like auto-complete for class and namespace names and easy class retrival with <code>Get-CimClass<\/code>.<\/p>\n<h3>Querying<\/h3>\n<p>Performing queries with the CIM Cmdlets is very pleasant. One line does it all.<\/p>\n<pre><code class=\"language-powershell\">$result = Get-CimInstance -Query \"Select * From Win32_Process Where Name = 'powershell.exe'\"<\/code><\/pre>\n<p>The parameters are very similar to the <code>Get-WmiObject<\/code> ones, and can be used as follows.<\/p>\n<pre><code class=\"language-powershell\">$result = Get-CimInstance -ClassName 'Win32_Process' -Filter \"Name = 'powershell.exe'\"<\/code><\/pre>\n<p>The <em>auto-complete<\/em> feature in Visual Studio Code.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-content\/uploads\/sites\/69\/2022\/08\/Get-CimInstance_autoComplete.png\" alt=\"Auto-Complete with CIM\" \/><\/p>\n<h3>Calling a WMI Method<\/h3>\n<p>The CIM Cmdlets introduces a unique way of calling WMI Methods. The results of a CIM query are called <strong>CimInstances<\/strong>, and you cannot call instance methods like you would with the other two options. Instead, you call another Cmdlet called <code>Invoke-CimMethod<\/code>.<\/p>\n<pre><code class=\"language-powershell\">$commandLine = 'powershell.exe -ExecutionPolicy Bypass -Command \"Write-Output ''Howdy! From WMI!''; Read-Host\"'\n$result = Get-CimClass -ClassName 'Win32_Process'\n$params = @{\n  MethodName = 'Create'\n  Arguments = @{\n    CommandLine = $commandLine\n  }\n}\n$output = $result | Invoke-CimMethod @params\n\n# Or\n$params = @{\n  ClassName = 'Win32_Process'\n  MethodName = 'Create'\n  Arguments = @{\n    CommandLine = $commandLine\n  }\n}\n$output = Invoke-CimMethod @params<\/code><\/pre>\n<p>And the result:<\/p>\n<pre><code class=\"language-powershell\">Invoke-CimMethod -ClassName 'Win32_Process' -MethodName 'Create' -Arguments @{ CommandLine = $commandLine }<\/code><\/pre>\n<pre><code class=\"language-Output\">ProcessId ReturnValue PSComputerName\n--------- ----------- --------------\n    14932           0<\/code><\/pre>\n<p>Have a hard time remembering parameters? Me too! Luckily <em>auto-complete<\/em> also works with them.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-content\/uploads\/sites\/69\/2022\/08\/Invoke-CimMethod_autoComplete.png\" alt=\"Auto-Complete with method parameters\" \/><\/p>\n<h3>Creating, Updating and Deleting a WMI Class Instance<\/h3>\n<p>If you used the old WMI Cmdlets before, this will look familiar.<\/p>\n<pre><code class=\"language-powershell\">$params = @{\n  Namespace = 'root\\SMS\\site_PS1'\n  ClassName = 'SMS_Collection'\n  Property = @{\n    Name = 'AwesomeDeviceCollection'\n    LimitingCollectionID = 'PS1000042'\n  }\n}\n$collection = New-CimInstance @params<\/code><\/pre>\n<p>Updating and deleting.<\/p>\n<pre><code class=\"language-powershell\">$params = @{\n  Namespace = 'root\\SMS\\site_PS1'\n  Query = \"Select * From SMS_Collection Where Name = 'AwesomeDeviceCollection'\"\n  Property = @{\n    Name = 'AwesomeDeviceCollection_NewName'\n  }\n}\nSet-CimInstance @params\n\n#Or\n\n$params = @{\n  Namespace = 'root\\SMS\\site_PS1'\n  Query = \"Select * From SMS_Collection Where Name = 'AwesomeDeviceCollection'\"\n}\n$collection = Get-CimInstance @params\n$collection | Set-CimInstance -Property @{\n  Name = 'AwesomeDeviceCollection_NewName'\n}\n\n#Deleting\n\n$params = @{\n  Namespace = 'root\\SMS\\site_PS1'\n  Query = \"Select * From SMS_Collection Where Name = 'AwesomeDeviceCollection'\"\n}\n$collection = Get-CimInstance @params\n$collection | Remove-CimInstance<\/code><\/pre>\n<h2>Pros and Cons<\/h2>\n<ul>\n<li>\n<p>The <strong>System.Management<\/strong> namespace is great for acquiring instances of objects or classes. The aliases like <code>[wmi]<\/code> or <code>[wmiclass]<\/code> makes it easy to work with them, if you know their path. Calling methods is also very intuitive. In the other hand, querying and doing more complex operations can be time-consuming, and can involve more objects to keep track of.<\/p>\n<\/li>\n<li>\n<p>Using the WMI Scripting API is great when you have to build whole scripts to manage WMI. Once you have the <strong>SWbemServices<\/strong> object you can work with pretty much anything else. It is also rewarding performance-wise, compared to the previous method, since you are working with the RCW interfaces directly. The <strong>System.Management<\/strong> namespace will wrap these interfaces to provide abstraction. But this comes at a cost. If you want to retrieve single objects or perform queries to analyze data, this method can be a little annoying to work with.<\/p>\n<\/li>\n<li>\n<p>The CIM cmdlets are number one on performance when querying multiple-object datasets. The <strong>CimInstance<\/strong> object is great to work with, specially when combining with other known objects, like the <strong>PSCustomObject<\/strong>. On the other hand, calling methods is not as straight forward as on the previous methods. And to access methods like <strong>Put<\/strong>, <strong>Get<\/strong> or <strong>Delete<\/strong> can be challenging.<\/p>\n<\/li>\n<\/ul>\n<h2>Bonus<\/h2>\n<p>Let&#8217;s create our own Namespace under root, and implement a custom class! We will use the <strong>System.Management<\/strong> namespace, but now you can use what you learn to implement this using the other methods as well.<\/p>\n<pre><code class=\"language-powershell\">$namespace = ([wmiclass]'root:__Namespace').CreateInstance()\n$namespace.Name = 'ScriptingBlogCoolNamespace'\n$namespace.Put()<\/code><\/pre>\n<pre><code class=\"language-Output\">Path          : \\\\.\\root:__NAMESPACE.Name=\"ScriptingBlogCoolNamespace\"\nRelativePath  : __NAMESPACE.Name=\"ScriptingBlogCoolNamespace\"\nServer        : .\nNamespacePath : root\nClassName     : __NAMESPACE\nIsClass       : False\nIsInstance    : True\nIsSingleton   : False<\/code><\/pre>\n<pre><code class=\"language-powershell\">$class = [wmiclass]::new('root\\ScriptingBlogCoolNamespace', '', $null)\n$class['__Class'] = 'CustomClass'\n$class.Qualifiers.Add('Static', $true)\n$class.Properties.Add('Source', 'Custom class with .NET!')\n$class.Properties.Add('PropertyKey', 1)\n## You need a Key property, otherwise WMI wouldn't be able to assemble the path of a new instance.\n$class.Properties['PropertyKey'].Qualifiers.Add('Key', $true)\n$class.Put()<\/code><\/pre>\n<pre><code class=\"language-Output\">Path          : \\\\.\\root\\ScriptingBlogCoolNamespace:CustomClass\nRelativePath  : CustomClass\nServer        : .\nNamespacePath : root\\ScriptingBlogCoolNamespace\nClassName     : CustomClass\nIsClass       : True\nIsInstance    : False\nIsSingleton   : False<\/code><\/pre>\n<pre><code class=\"language-powershell\">$instance = ([wmiclass]'root\\ScriptingBlogCoolNamespace:CustomClass').CreateInstance()\n$instance.Source = 'CustomInstance!'\n$instance.Put()<\/code><\/pre>\n<pre><code class=\"language-Output\">Path          : \\\\.\\root\\ScriptingBlogCoolNamespace:CustomClass.PropertyKey=1\nRelativePath  : CustomClass.PropertyKey=1\nServer        : .\nNamespacePath : root\\ScriptingBlogCoolNamespace\nClassName     : CustomClass\nIsClass       : False\nIsInstance    : True\nIsSingleton   : False<\/code><\/pre>\n<p>And just like that, we have our own Namespace, Class and Instance! The results on <strong>WmiExplorer<\/strong>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-content\/uploads\/sites\/69\/2022\/08\/NamespaceManiputlation.png\" alt=\"Namespace, Class and Instance\" \/><\/p>\n<h2>Conclusion<\/h2>\n<p>If you made it to the end, hopefully now you have the right tool for the right job, regarding WMI. There is no winner, all of them are good in specific situations. What they all have in common is that they, together, will make you a better System Administrator.<\/p>\n<p>Thank you for following along on this journey, and I see you next time!<\/p>\n<h2>Useful links<\/h2>\n<ul>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.management?view=dotnet-plat-ext-6.0\">System.Management<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/wmisdk\/scripting-api-for-wmi\">WMI Scripting API<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/powershell\/module\/cimcmdlets\/?view=powershell-7.2\">CIM Cmdlets<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/native-interop\/runtime-callable-wrapper\">Runtime-Callable Wrapper<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/wmisdk\/wmi-start-page\">WMI Documentation<\/a><\/li>\n<\/ul>\n<p>See what I am up to!<\/p>\n<p><a href=\"https:\/\/github.com\/FranciscoNabas\">Github<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Working with the different ways of managing WMI.<\/p>\n","protected":false},"author":62334,"featured_media":77,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[13],"tags":[7,77,3,78,34],"class_list":["post-746","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell","tag-net","tag-com","tag-powershell","tag-windows-management","tag-wmi"],"acf":[],"blog_post_summary":"<p>Working with the different ways of managing WMI.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts\/746","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/users\/62334"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/comments?post=746"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts\/746\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/media\/77"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/media?parent=746"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/categories?post=746"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/tags?post=746"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}