WMI query filters with PowerShell syntax instead of WQL

2465120031_ebb0a49e45_m
PowerShell comes already with tight integration to WMI with its built-in Get-WmiObject and Get-CimInstance cmdlets. One of the things that people already familiar with PowerShell syntax bothers about WMI is that it comes with its very own query language WQL. While WQL is very similar to SQL. Wouldn’t it be nicer if we could use the same operators and wild-card patterns we are already familiar with?
Well, for myself the answer is Yes:

PowerShellInsteadOfWQL
Let’s first build a proof of concept before creating a proxy function for Get-WmiObject. We can make us of the (newer) PowerShell parser to identify the different elements of the Filter for conversion and the WildcardPattern.ToWql method to convert the wild-card pattern for the LIKE operator:


function Get-WmiPowerShell {
[CmdletBinding()]
Param
(
[Parameter(Mandatory)]$Class,
[Parameter(Mandatory)]$Filter
)
$errors = $tokens = $null
$AST= [Management.Automation.Language.Parser]::ParseInput($filter.ToString().Replace('$null','NULL'), [ref]$tokens, [ref]$errors)
$tokens = $tokens | where { $_.Text -and $_.Name -ne '_' -and $_.Kind -ne 'Dot' }
$htReplacements=@'
eq = =
lt = <
gt = >
le = <=
ge = >=
ne = !=
like = like
and = and
or = or
is = is
isnot = is not
'@ | ConvertFrom-StringData
$wql = foreach ($token in $tokens) {
switch($token){
{ $_.Value -ne $null } { "'$(([Management.Automation.WildcardPattern]$_.Value).ToWql())'"; break }
{ $_.Kind -eq 'Parameter' } { $htReplacements."$($_.ParameterName)"; break }
{ [string]$_.TokenFlags -like '*BinaryOperator*' } { $htReplacements."$($_.Text.Replace('-',''))"; break }
{ $_.Kind -eq 'Variable' } { "'$(Get-Variable $_.Name -ValueOnly)'"; break }
Default { $_.Text }
}
}
$PSBoundParameters.Filter = $wql -join ' '
Get-WmiObject @PSBoundParameters
}

All it takes are 35 lines of code to implement the functionality.
Now that we have the proof of concept working we can go ahead and dynamically create the proxy functions for Get-WmiObject and Get-CimInstance to keep it simple we just add an additional parameter (PowerShellFilter) that takes the PowerShell syntax, converts the PowerShell to WQL and passes it on to the ‘Filter’ parameter without worrying about adding an additional parameter set to mutually exclude the ‘Filter’ and ‘PowerShellFilter’ parameters. After retrieving the code for the proxy command using the command meta data we need to add the statements for the new parameter (considering the same parameter sets as for the existing ‘Filter’ parameter) at the the top of the param statement and insert the additional logic between the following lines (e.g. Get-WmiObject)…

if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Get-WmiObject', [System.Management.Automation.CommandTypes]::Cmdlet)

… before creating the new function based on a script block made of the updated code:


foreach ($command in ('Get-WmiObject','Get-CimInstance')){
$Metadata = New-Object System.Management.Automation.CommandMetaData (Get-Command $command)
$proxyCmd = [System.Management.Automation.ProxyCommand]::Create($Metadata) #| clip
if ($command -eq 'Get-WmiObject'){
$newParam = @'
[Parameter(ParameterSetName='query')]
[ScriptBlock]
$PowerShellFilter,
'@
}
else{
$newParam = @'
[Parameter(ParameterSetName='ResourceUriComputerSet')]
[Parameter(ParameterSetName='ResourceUriSessionSet')]
[Parameter(ParameterSetName='ClassNameComputerSet')]
[Parameter(ParameterSetName='ClassNameSessionSet')]
[ScriptBlock]
$PowerShellFilter,
'@
}
$proxyCmd = $proxyCmd.Insert($proxyCmd.IndexOf('param(')+7,$newParam)
$newCode = @'
if ($PSBoundParameters.ContainsKey('PowerShellFilter')){
$errors = $tokens = $null
$AST= [Management.Automation.Language.Parser]::ParseInput($PowerShellFilter, [ref]$tokens, [ref]$errors)
$tokens = $tokens | where { $_.Text -and $_.Name -ne '_' -and $_.Kind -ne 'Dot' }
$htReplacements = @"
eq = =
lt = <
gt = >
le = <=
ge = >=
ne = !=
like = like
and = and
or = or
is = is
isnot = is not
"@ | ConvertFrom-StringData
$wql = foreach ($token in $tokens) {
switch($token){
{ $_.Value -ne $null } { "'$(([Management.Automation.WildcardPattern]$_.Value).ToWql())'"; break }
{ $_.Kind -eq 'Parameter' } { $htReplacements."$($_.ParameterName)"; break }
{ [string]$_.TokenFlags -like '*BinaryOperator*' } { $htReplacements."$($_.Text.Replace('-',''))"; break }
{ $_.Kind -eq 'Variable' } { "'$(Get-Variable $_.Name -ValueOnly)'"; break }
Default { $_.Text }
}
}
$null = $PSBoundParameters.Add('Filter',($wql -join ' '))
$null = $PSBoundParameters.Remove('PowerShellFilter')
}
'@
$proxyCmd = $proxyCmd.Insert($proxyCmd.IndexOf("['OutBuffer'] = 1")+28,$newCode)
Set-Item -Path function:$command -Value ([ScriptBlock]::Create($proxyCmd))
}

Putting the above into your profile (You can read here and here on how to work with profiles) will enable you to use PowerShell syntax with Get-WmiObject and Get-CimInstance:

$state = 'Running'
Get-WmiObject -Class Win32_Service -PowerShellFilter {Name -like '*srv*' -and State -eq $state}
Get-CimInstance -ClassName Win32_Service -PowerShellFilter {Name -like '*srv*' -and State -eq 'Running'}

How do you like PowerShell syntax to query WMI?

shareThoughts


Photo Credit: Svedek via Compfight cc

3 thoughts on “WMI query filters with PowerShell syntax instead of WQL

  1. Mind = Blown! How did you come up with this? I’m not gonna lie, this is high level… AST eh? Well ok, guess I have my next challenge then! The more I dig into PowerShell the deeper the rabbit hole seems to go… Any tips on how to get staretd with AST?

    Like

I'd love to hear what you think