In our first part I looked at CDXML basics and turned the WMI class Win32_OperatingSystem into a set of simple-to-use cmdlets like Get-WmiOperatingSystem
, Set-WmiOperatingSystem
, or Invoke-WmiOperatingSystemReboot
.
Even calling complex WMI methods like Win32ShutdownTracker() became trivial this way:
# forcefully logging off user with a message and timeout (admin privs required)
Invoke-WmiOperatingSystemWin32ShutdownTracker -Flags Logoff,Force -Timeout 30 -Comment 'I log you off in 30 seconds because I can!' -ReasonCode 99
To use all these new cmdlets (like
Invoke-WmiOperatingSystemWin32ShutdownTracker
in the example above), simply import the CDXML I created last time (usingImport-Module
). It’s really simple and takes only 10 seconds.
In this second part, I’ll look at some more advanced features. This time, I’ll focus on the WMI class Win32_Service which represents all services on your system.
Yes, there is a built-in cmdlet
Get-Service
that you can use to query services. However, the WMI approach, and the CDXML cmdlets likeGet-WmiService
we are about to create are much more powerful.They return a lot more information, and they are fully remotable, so you can run them locally or on one or multiple remote systems.
Get-Service
has only very limited built-in remoting capabilities, and while you can use the universal PowerShell Remoting viaInvoke-Command
, this causes way more overhead than the light-weight remoting built into WMI cmdlets.
The WMI class Win32_OperatingSystem always returns exactly one instance so there was no need for sophisticated querying.
With Win32_Service, that is different. There are hundreds of instances, each representing exactly one service on your system.
Why Manual Queries Suck
Of course you can always use PowerShell’s built-in filtering via Where-Object
to query for the service you need:
# finding a specific service
Get-CimInstance -ClassName Win32_Service | Where-Object Name -eq 'Spooler'
However this client-side filtering is evil and only a last resort.
Client-Side Filtering Wastes Resources
With the code above, you have asked WMI to provide information about all services, and then you dumped most of the information and just picked the instance where the name matched your requested service name.
This is wasting resources, especially when you do remote queries:
# finding a specific remote service
Get-CimInstance -ClassName Win32_Service -ComputerName webserver12 | Where-Object Name -eq 'Spooler'
This example assumes that your current account has remoting permissions on the target machine, and that remoting has been setup properly. If remote access doesn’t work for you, go over the prerequisites.
WMI on the target machine is now transferring information about all services across the network to you, and you are dumping most of it.
Server-Side Queries Are Better, But…
A much better approach uses server-side queries and asks WMI to produce only the data you really need:
# finding a specific service via server side query:
Get-CimInstance -ClassName Win32_Service -Filter 'Name="Spooler"'
Get-CimInstance -Query 'Select * From Win32_Service Where Name="Spooler"'
This approach does not need the client-side filter Where-Object
anymore and is much more efficient. However there is a “but”. Server-side filters do not run within PowerShell. Instead, they run inside whoever produces the data. In our case, the queries run inside WMI.
This is why the filter expression is not using PowerShell and its operators. Instead, a query language named WQL is used (which is very similar to the common database query language SQL). It may become complex to compose WMI filters, and you’d need to use the WQL Operators.
CDXML: Autogenerating Cmdlets
Fortunately, CDXML can produce powerful PowerShell cmdlets that make querying really simple and fast.
Creating Get-WmiService
Here is the XML definition for Get-WmiService
in the most simple way, similar to what I did in part 1:
<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">
<Class ClassName="Root/CIMV2/Win32_Service" ClassVersion="2.0">
<Version>1.0</Version>
<DefaultNoun>WmiService</DefaultNoun>
<InstanceCmdlets>
<GetCmdletParameters />
</InstanceCmdlets>
</Class>
</PowerShellMetadata>
The key information is the ClassName (pointing to the WMI namespace and classname I am about to use) and the DefaultNoun for the cmdlets that CDXML will generate for us.
Let’s write the XML to file and import it as a PowerShell module:
$testfolder = "$env:temp\cdxmlTest"
$cdxml = @'
<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">
<Class ClassName="Root/CIMV2/Win32_Service" ClassVersion="2.0">
<Version>1.0</Version>
<DefaultNoun>WmiService</DefaultNoun>
<InstanceCmdlets>
<GetCmdletParameters />
</InstanceCmdlets>
</Class>
</PowerShellMetadata>
'@
# create a test folder if it does not exist
$exists = Test-Path -Path $testfolder -PathType Container
if (!$exists) { $null = New-Item -Path $testfolder -ItemType Directory }
# write cdxml file
$path = Join-Path -Path $testfolder -ChildPath 'win32_service.cdxml'
$cdxml | Set-Content -Path $path -Encoding UTF8
# import the module
Import-Module -Name $path -Verbose -Force
When you run this, auto-magically a new module named Win32_Service is produced and contains one cmdlet named Get-WmiService
:
VERBOSE: Loading module from path 'C:\Users\tobia\AppData\Local\Temp\cdxmlTest\win32_service.cdxml'.
VERBOSE: Importing function 'Get-WmiService'.
And when you run Get-WmiService
, it returns all instances of Win32_Service:
Get-WmiService | Select-Object -Property *
That’s a lot more information than Get-Service
returns. That’s good:
Name : AdobeARMservice
Status : OK
ExitCode : 0
DesktopInteract : False
ErrorControl : Ignore
PathName : "C:\Program Files (x86)\Common Files\Adobe\ARM\1.0\armsvc.exe"
ServiceType : Own Process
StartMode : Auto
Caption : Adobe Acrobat Update Service
Description : Adobe Acrobat Updater keeps your Adobe software up to date.
InstallDate :
CreationClassName : Win32_Service
Started : True
SystemCreationClassName : Win32_ComputerSystem
SystemName : DELL7390
AcceptPause : False
AcceptStop : True
DisplayName : Adobe Acrobat Update Service
ServiceSpecificExitCode : 0
StartName : LocalSystem
State : Running
TagId : 0
CheckPoint : 0
DelayedAutoStart : False
ProcessId : 4244
WaitHint : 0
PSComputerName :
CimClass : Root/CIMV2:Win32_Service
CimInstanceProperties : {Caption, Description, InstallDate, Name...}
CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties
...
But where is the advanced querying? Get-WmiService
always returns all service instances.
Adding Advanced Querying
Let’s add sophisticated query parameters. In the previous XML definition, the Get-cmdlet was automatically created for us. Let’s define it manually. I am adding a node GetCmdlet below.
With Verb, you would now be able to change the default verb to anything you like but obviously Get was a good choice, so I am keeping this. The interesting part starts in the node QueryableProperties:
<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">
<Class ClassName="Root/CIMV2/Win32_Service" ClassVersion="2.0">
<Version>1.0</Version>
<DefaultNoun>WmiService</DefaultNoun>
<InstanceCmdlets>
<GetCmdletParameters />
<GetCmdlet>
<CmdletMetadata Verb="Get" />
<GetCmdletParameters DefaultCmdletParameterSet="ByName">
<QueryableProperties>
<Property PropertyName="AcceptPause">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="AcceptStop">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="CheckPoint">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="CreationClassName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="DelayedAutoStart">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="Description">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="DesktopInteract">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="DisplayName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ErrorControl">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Ignore</AllowedValue>
<AllowedValue>Normal</AllowedValue>
<AllowedValue>Severe</AllowedValue>
<AllowedValue>Critical</AllowedValue>
<AllowedValue>Unknown</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="ExitCode">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="InstallDate">
<Type PSType="system.datetime" />
<MinValueQuery>
<CmdletParameterMetadata PSName="BeforeInstallDate" />
</MinValueQuery>
<MaxValueQuery>
<CmdletParameterMetadata PSName="AfterInstallDate" />
</MaxValueQuery>
</Property>
<Property PropertyName="Name">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata Position="0" CmdletParameterSets="ByName" IsMandatory="false" />
</RegularQuery>
<ExcludeQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" PSName="ExcludeName" CmdletParameterSets="ByName" />
</ExcludeQuery>
</Property>
<Property PropertyName="PathName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ProcessId">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata Position="0" CmdletParameterSets="ByProcessId" IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ServiceSpecificExitCode">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ServiceType">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Kernel Driver</AllowedValue>
<AllowedValue>File System Driver</AllowedValue>
<AllowedValue>Adapter</AllowedValue>
<AllowedValue>Recognizer Driver</AllowedValue>
<AllowedValue>Own Process</AllowedValue>
<AllowedValue>Share Process</AllowedValue>
<AllowedValue>Interactive Process</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="Started">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="StartMode">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Boot</AllowedValue>
<AllowedValue>System</AllowedValue>
<AllowedValue>Auto</AllowedValue>
<AllowedValue>Manual</AllowedValue>
<AllowedValue>Disabled</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="StartName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="State">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Stopped</AllowedValue>
<AllowedValue>Start Pending</AllowedValue>
<AllowedValue>Stop Pending</AllowedValue>
<AllowedValue>Running</AllowedValue>
<AllowedValue>Continue Pending</AllowedValue>
<AllowedValue>Pause Pending</AllowedValue>
<AllowedValue>Paused</AllowedValue>
<AllowedValue>Unknown</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="Status">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="TagId">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="WaitHint">
<Type PSType="system.uint32" />
<MinValueQuery>
<CmdletParameterMetadata PSName="MinWaitHint" />
</MinValueQuery>
<MaxValueQuery>
<CmdletParameterMetadata PSName="MaxWaitHint" />
</MaxValueQuery>
</Property>
</QueryableProperties>
</GetCmdletParameters>
</GetCmdlet>
</InstanceCmdlets>
</Class>
</PowerShellMetadata>
QueryableProperties contains a long list of parameter definitions. Each is a query that you can later use to search for instances. Inside of each parameter, there is a definition for the type of query you want.
Run the script below to update your module Win32_Service and produce a better Get-WmiService
cmdlet:
$testfolder = "$env:temp\cdxmlTest"
$cdxml = @'
<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">
<Class ClassName="Root/CIMV2/Win32_Service" ClassVersion="2.0">
<Version>1.0</Version>
<DefaultNoun>WmiService</DefaultNoun>
<InstanceCmdlets>
<GetCmdletParameters />
<GetCmdlet>
<CmdletMetadata Verb="Get" />
<GetCmdletParameters DefaultCmdletParameterSet="ByName">
<QueryableProperties>
<Property PropertyName="AcceptPause">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="AcceptStop">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="CheckPoint">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="CreationClassName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="DelayedAutoStart">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="Description">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="DesktopInteract">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="DisplayName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ErrorControl">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Ignore</AllowedValue>
<AllowedValue>Normal</AllowedValue>
<AllowedValue>Severe</AllowedValue>
<AllowedValue>Critical</AllowedValue>
<AllowedValue>Unknown</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="ExitCode">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="InstallDate">
<Type PSType="system.datetime" />
<MinValueQuery>
<CmdletParameterMetadata PSName="BeforeInstallDate" />
</MinValueQuery>
<MaxValueQuery>
<CmdletParameterMetadata PSName="AfterInstallDate" />
</MaxValueQuery>
</Property>
<Property PropertyName="Name">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata Position="0" CmdletParameterSets="ByName" IsMandatory="false" />
</RegularQuery>
<ExcludeQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" PSName="ExcludeName" CmdletParameterSets="ByName" />
</ExcludeQuery>
</Property>
<Property PropertyName="PathName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ProcessId">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata Position="0" CmdletParameterSets="ByProcessId" IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ServiceSpecificExitCode">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ServiceType">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Kernel Driver</AllowedValue>
<AllowedValue>File System Driver</AllowedValue>
<AllowedValue>Adapter</AllowedValue>
<AllowedValue>Recognizer Driver</AllowedValue>
<AllowedValue>Own Process</AllowedValue>
<AllowedValue>Share Process</AllowedValue>
<AllowedValue>Interactive Process</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="Started">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="StartMode">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Boot</AllowedValue>
<AllowedValue>System</AllowedValue>
<AllowedValue>Auto</AllowedValue>
<AllowedValue>Manual</AllowedValue>
<AllowedValue>Disabled</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="StartName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="State">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Stopped</AllowedValue>
<AllowedValue>Start Pending</AllowedValue>
<AllowedValue>Stop Pending</AllowedValue>
<AllowedValue>Running</AllowedValue>
<AllowedValue>Continue Pending</AllowedValue>
<AllowedValue>Pause Pending</AllowedValue>
<AllowedValue>Paused</AllowedValue>
<AllowedValue>Unknown</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="Status">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="TagId">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="WaitHint">
<Type PSType="system.uint32" />
<MinValueQuery>
<CmdletParameterMetadata PSName="MinWaitHint" />
</MinValueQuery>
<MaxValueQuery>
<CmdletParameterMetadata PSName="MaxWaitHint" />
</MaxValueQuery>
</Property>
</QueryableProperties>
</GetCmdletParameters>
</GetCmdlet>
</InstanceCmdlets>
</Class>
</PowerShellMetadata>
'@
# create a test folder if it does not exist
$exists = Test-Path -Path $testfolder -PathType Container
if (!$exists) { $null = New-Item -Path $testfolder -ItemType Directory }
# write cdxml file
$path = Join-Path -Path $testfolder -ChildPath 'win32_service.cdxml'
$cdxml | Set-Content -Path $path -Encoding UTF8
# import the module
Import-Module -Name $path -Verbose -Force
Now querying is super easy, and you can combine all of the query parameters to produce very complex queries - and these queries automatically run on the server-side:
# query for all services that start with "S", are running, and can interact with the user
Get-WmiService -DesktopInteract -Name S* -State Running
If you wanted to compose this query manually that would be a monster task with a lot of expert knowledge (i.e. you’d need to know the property names, the WQL operators, and make sure you use the special wildcard character %):
Get-CimInstance -ClassName Win32_Service -Filter 'DesktopInteract=TRUE AND Name LIKE "S%" AND State="Running"'
Let’s take a quick look at the different query types you can use for your parameters:
Support for Switch Parameters
A RegularQuery matches the value you submit to the parameter. If the value you want to query is a simple boolean value, you can ask PowerShell to add a switch parameter. Here is an example:
<Property PropertyName="DesktopInteract">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
The parameter DesktopInteract is a switch parameter, and when you set it, all services are returned where the property DesktopInteract is true. So it will be trivial to find services that can interact with the user:
Get-WmiService -DesktopInteract
Support For Wildcards
Now take a look at the parameter Name:
<Property PropertyName="Name">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata Position="0" CmdletParameterSets="ByName" IsMandatory="false" />
</RegularQuery>
<ExcludeQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" PSName="ExcludeName" CmdletParameterSets="ByName" />
</ExcludeQuery>
</Property>
It matches the instance property Name, so you can now easily search for service names. And because AllowGlobbing is set to $true
, wildcards are permitted:
# finding all services that start with "S"
Get-WmiService -Name S*
Support For Excludes
The parameter Name uses a second query ExcludeQuery which does the opposite. So to find all services that do not start with S, run this:
# finding all services that do not start with "S"
Get-WmiService -Name * -ExcludeName S*
Support for Enumerations
Some properties typically contain one of a predefined list of keywords. For example StartMode can only be one of five predefined start modes. Take a look at the property StartMode:
<Property PropertyName="StartMode">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Boot</AllowedValue>
<AllowedValue>System</AllowedValue>
<AllowedValue>Auto</AllowedValue>
<AllowedValue>Manual</AllowedValue>
<AllowedValue>Disabled</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
The parameter supports a ValidateSet, much similar to your own hand-written PowerShell functions, so not only can you query for services with a given start mode. PowerShell also supports auto-completion and intellisense:
# finding all autostart services
Get-WmiService -StartMode Auto
Support For Ranges
When properties contain numbers or dates, you often don’t want to check for a specific number or date but rather use queries that search for numbers less or greater than a value, or before or after a date.
Have a look at the property InstallDate:
<Property PropertyName="InstallDate">
<Type PSType="system.datetime" />
<MinValueQuery>
<CmdletParameterMetadata PSName="BeforeInstallDate" />
</MinValueQuery>
<MaxValueQuery>
<CmdletParameterMetadata PSName="AfterInstallDate" />
</MaxValueQuery>
</Property>
Here, Get-WmiObject
does not implement the search parameter -InstallDate but rather -BeforeInstallDate and -AfterInstallDate using a MinValueQuery and MaxValueQuery. So you now can easily find services installed before or after a given date:
Get-WmiService -AfterInstallDate '2020-01-01'
Unfortunately, most services do not maintain their InstallDate so this property is typically empty.
Calling Methods
Win32_Service comes with 11 methods that you can use for example to start, stop, pause, or resume services. All of this can also be achieved using the built-in PowerShell cmdlets like Start-Service
or Set-Service
.
However keep in mind: the WMI methods always have the advantage of its built-in remoting support, and some of the methods cover areas that aren’t available with the built-in cmdlets.
I am not going over the details of implementing methods again (please return to part 1 for a refresher). I do want to cover one new feature: returning information from a method.
Returning Information
Most WMI methods invoke some action but they typically always return a default status object with an error code that tells you if the call was successful.
Only a few methods actually return rich information. Win32_Service provides the method GetSecurityDescriptor() which returns the security information attached to a service. This is the Xml definition for a new cmdlet Get-WmiServiceSecurityDescriptor
that calls this method:
<Cmdlet>
<CmdletMetadata Verb="Get" Noun="WmiServiceSecurityDescriptor" ConfirmImpact="Low" />
<!--defining the WMI instance method used by this cmdlet:-->
<Method MethodName="GetSecurityDescriptor">
<ReturnValue>
<Type PSType="system.uint32" />
<CmdletOutputMetadata>
<ErrorCode />
</CmdletOutputMetadata>
</ReturnValue>
<Parameters>
<!--Method returns an instance of Win32_SecurityDescriptor in property Descriptor-->
<Parameter ParameterName="Descriptor">
<Type PSType="Microsoft.Management.Infrastructure.CimInstance[]" />
<CmdletOutputMetadata />
</Parameter>
</Parameters>
</Method>
</Cmdlet>
The important part takes place in the node Parameters: Typically, this node defines the input parameters that go into the method you want to call. The method GetSecurityDescriptor does not require any parameters so why is this section still present?
It turns out that the node Parameters also defines the output parameter. If a method call returns additional information, this information surfaces in the generic return object that all methods return.
When you call the method GetSecurityDescriptor manually using the CIM Cmdlets, you can see this:
$query = 'Select * From Win32_Service where Name="Spooler"'
Invoke-CimMethod -Query $query -MethodName GetSecurityDescriptor |
Add-Member -MemberType ScriptProperty -Name ReturnValueFriendly -Passthru -Value {
switch ([int]$this.ReturnValue)
{
0 {'Success'}
2 {'Access denied'}
8 {'Unknown failure'}
9 {'Privilege missing'}
21 {'Invalid parameter'}
default {"Unknown Error $_"}
}
}
Make sure you run this code with Administrator privileges! Regular users cannot read security-related information.
The result looks like this:
Descriptor ReturnValue PSComputerName ReturnValueFriendly
---------- ----------- -------------- -------------------
Win32_SecurityDescriptor 0 Success
The real return information can be found in the property Descriptor. And that is exactly what can be found in the property definition:
<Parameter ParameterName="Descriptor">
<Type PSType="Microsoft.Management.Infrastructure.CimInstance[]" />
<CmdletOutputMetadata />
</Parameter>
It tells PowerShell that the property Descriptor yields instances of type Microsoft.Management.Infrastructure.CimInstance.
To test-drive Get-WmiServiceSecurityDescriptor
, run this:
$testfolder = "$env:temp\cdxmlTest"
$cdxml = @'
<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">
<Class ClassName="Root/CIMV2/Win32_Service" ClassVersion="2.0">
<Version>1.0</Version>
<DefaultNoun>WmiService</DefaultNoun>
<InstanceCmdlets>
<GetCmdletParameters />
<GetCmdlet>
<CmdletMetadata Verb="Get" />
<GetCmdletParameters DefaultCmdletParameterSet="ByName">
<QueryableProperties>
<Property PropertyName="AcceptPause">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="AcceptStop">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="CheckPoint">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="CreationClassName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="DelayedAutoStart">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="Description">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="DesktopInteract">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="DisplayName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ErrorControl">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Ignore</AllowedValue>
<AllowedValue>Normal</AllowedValue>
<AllowedValue>Severe</AllowedValue>
<AllowedValue>Critical</AllowedValue>
<AllowedValue>Unknown</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="ExitCode">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="InstallDate">
<Type PSType="system.datetime" />
<MinValueQuery>
<CmdletParameterMetadata PSName="BeforeInstallDate" />
</MinValueQuery>
<MaxValueQuery>
<CmdletParameterMetadata PSName="AfterInstallDate" />
</MaxValueQuery>
</Property>
<Property PropertyName="Name">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata Position="0" CmdletParameterSets="ByName" IsMandatory="false" />
</RegularQuery>
<ExcludeQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" PSName="ExcludeName" CmdletParameterSets="ByName" />
</ExcludeQuery>
</Property>
<Property PropertyName="PathName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ProcessId">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata Position="0" CmdletParameterSets="ByProcessId" IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ServiceSpecificExitCode">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ServiceType">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Kernel Driver</AllowedValue>
<AllowedValue>File System Driver</AllowedValue>
<AllowedValue>Adapter</AllowedValue>
<AllowedValue>Recognizer Driver</AllowedValue>
<AllowedValue>Own Process</AllowedValue>
<AllowedValue>Share Process</AllowedValue>
<AllowedValue>Interactive Process</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="Started">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="StartMode">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Boot</AllowedValue>
<AllowedValue>System</AllowedValue>
<AllowedValue>Auto</AllowedValue>
<AllowedValue>Manual</AllowedValue>
<AllowedValue>Disabled</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="StartName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="State">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Stopped</AllowedValue>
<AllowedValue>Start Pending</AllowedValue>
<AllowedValue>Stop Pending</AllowedValue>
<AllowedValue>Running</AllowedValue>
<AllowedValue>Continue Pending</AllowedValue>
<AllowedValue>Pause Pending</AllowedValue>
<AllowedValue>Paused</AllowedValue>
<AllowedValue>Unknown</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="Status">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="TagId">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="WaitHint">
<Type PSType="system.uint32" />
<MinValueQuery>
<CmdletParameterMetadata PSName="MinWaitHint" />
</MinValueQuery>
<MaxValueQuery>
<CmdletParameterMetadata PSName="MaxWaitHint" />
</MaxValueQuery>
</Property>
</QueryableProperties>
</GetCmdletParameters>
</GetCmdlet>
<!--Get-ServiceSecurityDescriptor: invoking method GetSecurityDescriptor():-->
<Cmdlet>
<!--defining the ConfirmImpact which indicates how severe the changes are that this cmdlet performs-->
<CmdletMetadata Verb="Get" Noun="WmiServiceSecurityDescriptor" ConfirmImpact="Low" />
<!--defining the WMI instance method used by this cmdlet:-->
<Method MethodName="GetSecurityDescriptor">
<ReturnValue>
<Type PSType="system.uint32" />
<CmdletOutputMetadata>
<ErrorCode />
</CmdletOutputMetadata>
</ReturnValue>
<!--defining the parameters of this cmdlet:-->
<Parameters>
<!--Method returns an instance of Win32_SecurityDescriptor in property Descriptor-->
<Parameter ParameterName="Descriptor">
<Type PSType="Microsoft.Management.Infrastructure.CimInstance[]" />
<CmdletOutputMetadata />
</Parameter>
</Parameters>
</Method>
</Cmdlet>
</InstanceCmdlets>
</Class>
</PowerShellMetadata>
'@
# create a test folder if it does not exist
$exists = Test-Path -Path $testfolder -PathType Container
if (!$exists) { $null = New-Item -Path $testfolder -ItemType Directory }
# write cdxml file
$path = Join-Path -Path $testfolder -ChildPath 'win32_service.cdxml'
$cdxml | Set-Content -Path $path -Encoding UTF8
# import the module
Import-Module -Name $path -Verbose -Force
As it turns out, Get-WmiServiceSecurityDescriptor
has no own query parameters so you’d have to combine it with Get-WmiService
like this:
Get-WmiService -Name spooler | Get-WmiServiceSecurityDescriptor
Make sure you run this with full Administrator privileges.
Thanks to the output parameter definition, Get-WmiServiceSecurityDescriptor
directly returns the security information:
ControlFlags : 32788
DACL : {Win32_ACE, Win32_ACE, Win32_ACE}
Group : Win32_Trustee
Owner : Win32_Trustee
SACL : {Win32_ACE}
TIME_CREATED :
PSComputerName :
Final Version: Module Win32_Service
To finalize the module, wouldn’t it be nice if Get-WmiServiceSecurityDescriptor
could run stand-alone, too? Up until now, all method cmdlets can only work when you pipe instances into them:
# current implementation requires pipeline:
Get-WmiService -Name spooler | Get-WmiServiceSecurityDescriptor
# with query parameters, method cmdlets could work standlone, too:
Get-WmiServiceSecurityDescriptor -Name spooler
Adding Query Parameters For Methods
For this, you’d just have to add more QueryParameters. We already defined a lot of them for GetCmdlet.
You have two choices:
- All Instance Methods: add a node GetCmdletParameters to InstanceCmdlets. These query parameters are then available to all instance methods.
- Specific Method: or add a node GetCmdletParameters to the actual Method node of the method where you want them to be added. Query parameters would apply only to that specific method.
Limited Set Of Query Parameters
For the GetCmdlet Get-WmiService
, it was a good idea to add as many query parameters as possible, and to be as flexible as possible and include wildcard support. That’s because Get-WmiService
is typically used to search for services, and being able to use a variety of search parameters is helpful.
For cmdlets that invoke methods, you should be a lot more restrictive. It makes no sense and can actually be harmful if your query parameters selected a large number of services that then would all invoke the method.
Instead, you should limit yourself to one distinct query parameter, and not allow wildcards - forcing the user to specify the exact service name.
With this restrictive approach, you still would have the full querying flexibility when you use
Get-WmiService
with its rich querying and pipe the results to other cmdlets.
So I am adding a query parameter Name to all instance cmdlets, and I am setting AllowGlobbing to $false
(wildcards not permitted).
As you see, you can use the same query parameter (in this case Name) multiple times: for the GetCmdlet, it would allow wildcards, and for method invocation cmdlets, it would not.
You can use -Name with wildcards, but when you use Get-WmiServiceSecurityDescriptor
, you must specify the exact name:
<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">
<Class ClassName="Root/CIMV2/Win32_Service" ClassVersion="2.0">
<Version>1.0</Version>
<DefaultNoun>WmiService</DefaultNoun>
<InstanceCmdlets>
<GetCmdletParameters DefaultCmdletParameterSet="ByName">
<QueryableProperties>
<Property PropertyName="Name">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata Position="0" CmdletParameterSets="ByName" IsMandatory="false" />
</RegularQuery>
</Property>
</QueryableProperties>
</GetCmdletParameters>
<GetCmdlet>
<CmdletMetadata Verb="Get" />
...
Here is another update to your CDXML module which now also contains a method Get-WmiServiceSecurityDescriptor
:
$testfolder = "$env:temp\cdxmlTest"
$cdxml = @'
<?xml version="1.0" encoding="utf-8"?>
<PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">
<Class ClassName="Root/CIMV2/Win32_Service" ClassVersion="2.0">
<Version>1.0</Version>
<DefaultNoun>WmiService</DefaultNoun>
<InstanceCmdlets>
<GetCmdletParameters DefaultCmdletParameterSet="ByName">
<QueryableProperties>
<Property PropertyName="Name">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata Position="0" CmdletParameterSets="ByName" IsMandatory="false" />
</RegularQuery>
</Property>
</QueryableProperties>
</GetCmdletParameters>
<GetCmdlet>
<CmdletMetadata Verb="Get" />
<GetCmdletParameters DefaultCmdletParameterSet="ByName">
<QueryableProperties>
<Property PropertyName="AcceptPause">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="AcceptStop">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="CheckPoint">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="CreationClassName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="DelayedAutoStart">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="Description">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="DesktopInteract">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="DisplayName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ErrorControl">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Ignore</AllowedValue>
<AllowedValue>Normal</AllowedValue>
<AllowedValue>Severe</AllowedValue>
<AllowedValue>Critical</AllowedValue>
<AllowedValue>Unknown</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="ExitCode">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="InstallDate">
<Type PSType="system.datetime" />
<MinValueQuery>
<CmdletParameterMetadata PSName="BeforeInstallDate" />
</MinValueQuery>
<MaxValueQuery>
<CmdletParameterMetadata PSName="AfterInstallDate" />
</MaxValueQuery>
</Property>
<Property PropertyName="Name">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata Position="0" CmdletParameterSets="ByName" IsMandatory="false" />
</RegularQuery>
<ExcludeQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" PSName="ExcludeName" CmdletParameterSets="ByName" />
</ExcludeQuery>
</Property>
<Property PropertyName="PathName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ProcessId">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata Position="0" CmdletParameterSets="ByProcessId" IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ServiceSpecificExitCode">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="ServiceType">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Kernel Driver</AllowedValue>
<AllowedValue>File System Driver</AllowedValue>
<AllowedValue>Adapter</AllowedValue>
<AllowedValue>Recognizer Driver</AllowedValue>
<AllowedValue>Own Process</AllowedValue>
<AllowedValue>Share Process</AllowedValue>
<AllowedValue>Interactive Process</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="Started">
<Type PSType="switch" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="StartMode">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Boot</AllowedValue>
<AllowedValue>System</AllowedValue>
<AllowedValue>Auto</AllowedValue>
<AllowedValue>Manual</AllowedValue>
<AllowedValue>Disabled</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="StartName">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="State">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false">
<ValidateSet>
<AllowedValue>Stopped</AllowedValue>
<AllowedValue>Start Pending</AllowedValue>
<AllowedValue>Stop Pending</AllowedValue>
<AllowedValue>Running</AllowedValue>
<AllowedValue>Continue Pending</AllowedValue>
<AllowedValue>Pause Pending</AllowedValue>
<AllowedValue>Paused</AllowedValue>
<AllowedValue>Unknown</AllowedValue>
</ValidateSet>
</CmdletParameterMetadata>
</RegularQuery>
</Property>
<Property PropertyName="Status">
<Type PSType="system.string" />
<RegularQuery AllowGlobbing="true">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="TagId">
<Type PSType="system.uint32" />
<RegularQuery AllowGlobbing="false">
<CmdletParameterMetadata IsMandatory="false" />
</RegularQuery>
</Property>
<Property PropertyName="WaitHint">
<Type PSType="system.uint32" />
<MinValueQuery>
<CmdletParameterMetadata PSName="MinWaitHint" />
</MinValueQuery>
<MaxValueQuery>
<CmdletParameterMetadata PSName="MaxWaitHint" />
</MaxValueQuery>
</Property>
</QueryableProperties>
</GetCmdletParameters>
</GetCmdlet>
<!--Get-ServiceSecurityDescriptor: invoking method GetSecurityDescriptor():-->
<Cmdlet>
<!--defining the ConfirmImpact which indicates how severe the changes are that this cmdlet performs-->
<CmdletMetadata Verb="Get" Noun="WmiServiceSecurityDescriptor" ConfirmImpact="Low" />
<!--defining the WMI instance method used by this cmdlet:-->
<Method MethodName="GetSecurityDescriptor">
<ReturnValue>
<Type PSType="system.uint32" />
<CmdletOutputMetadata>
<ErrorCode />
</CmdletOutputMetadata>
</ReturnValue>
<!--defining the parameters of this cmdlet:-->
<Parameters>
<!--Method returns an instance of Win32_SecurityDescriptor in property Descriptor-->
<Parameter ParameterName="Descriptor">
<Type PSType="Microsoft.Management.Infrastructure.CimInstance[]" />
<CmdletOutputMetadata />
</Parameter>
</Parameters>
</Method>
</Cmdlet>
</InstanceCmdlets>
</Class>
</PowerShellMetadata>
'@
# create a test folder if it does not exist
$exists = Test-Path -Path $testfolder -PathType Container
if (!$exists) { $null = New-Item -Path $testfolder -ItemType Directory }
# write cdxml file
$path = Join-Path -Path $testfolder -ChildPath 'win32_service.cdxml'
$cdxml | Set-Content -Path $path -Encoding UTF8
# import the module
Import-Module -Name $path -Verbose -Force
Using The New Cmdlets
Once you run this, the newly created module Win32_Service now provides you with two super-useful new cmdlets:
Get-WmiService
returns services to you, and you have a plethora of query parameters that you can use to find exactly the services you need. All query parameters automatically compose a server-side WMI query that is much more efficient than using Where-Object
.
Get-WmiServiceSecurityDescriptor
returns the security descriptor of one or more services. You can now run it stand-alone like this:
Get-WmiServiceSecurityDescriptor -Name spooler, wuauserv
The result looks like this:
ControlFlags : 32788
DACL : {Win32_ACE, Win32_ACE, Win32_ACE}
Group : Win32_Trustee
Owner : Win32_Trustee
SACL : {Win32_ACE}
TIME_CREATED :
PSComputerName :
ControlFlags : 32788
DACL : {Win32_ACE, Win32_ACE, Win32_ACE}
Group : Win32_Trustee
Owner : Win32_Trustee
SACL : {Win32_ACE}
TIME_CREATED :
PSComputerName :
Plus, all CDXML-defined cmdlets come with rich and powerful remoting capabilities.
What’s Next
In this article I focused only on key parts of CDXML-generated PowerShell modules. Ultimately, I’ll publish a free module soon that wraps all classic WMI classes and produces hundreds of useful cmdlets.
Until we get there, there’s still some ground to cover. In the next part, I’ll look at static methods that you can use to create new things.