Find Files with WMI and PowerShell Revisited

computereyeLast week I posted a PowerShell function to find files using WMI. One of the comments I got was about finding files with wildcards. In WMI, we’ve been able to search via wildcards and the LIKE operator since the days of XP.

In a WMI query we use the % as the wildcard character. Here’s an example:

PS C:\scripts> get-wmiobject win32_service -filter "displayname LIKE 'Micro%'" | Select Name,Displayname,State,Startmode

Name                     Displayname              State                    Startmode
----                     -----------              -----                    ---------
MSiSCSI                  Microsoft iSCSI Initi... Stopped                  Manual
swprv                    Microsoft Software Sh... Stopped                  Manual
wlidsvc                  Microsoft Account Sig... Stopped                  Manual

So it wasn’t especially difficult to revise my original function to accept wildcards as part of the file name. Since we are used to using * as the wildcard character, I assumed it would be used here as well. So all I had to do was replace the * with % and change the operator.

#if there is * in the filename or extension, replace it with %
#and change the comparison operator for the WMI query
if ($filename -match "\*" ) {
    Write-Verbose "Wildcard search on filename"
    $filename = $filename.Replace("*","%")
    $fileOp="LIKE"
}
else {
    $fileOp="="
}
if ($extension -match "\*") {
    Write-Verbose "Wildcard search on extension"
    $extension = $extension.Replace("*","%")
    $extOp="LIKE"
}
else {
    $extOp="="
}
$filter = "Filename $fileOp '$filename' AND extension $extOp '$extension' AND Drive='$drive'"
Write-Verbose $filter

The reason I didn’t simply make the expression use LIKE all the time is performance. When you use the LIKE operator, there is a significant performance price to pay. So I only wanted to use LIKE if I really had to.

The other change I made was to accept an alternate credential (using a technique Boe Prox turned me on to).

...
[System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty,
...

If you pass a string like mydomain\admin, you’ll get prompted for the password. Or you can pass a previously created credential object. Here’s the revised code, less the comment based help.

#requires -version 2.0

Function Get-CIMFile {

[cmdletbinding(DefaultParameterSetName="Default")]
Param(
[Parameter(Position=0,Mandatory=$True,HelpMessage="What is the name of the file?")]
[ValidateNotNullorEmpty()]
[alias("file")]
[string]$Name,
[ValidateNotNullorEmpty()]
[string]$Drive="C:",
[ValidateNotNullorEmpty()]
[string[]]$Computername=$env:computername,
[System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty,
[Parameter(ParameterSetName="Job")]
[switch]$AsJob,
[Parameter(ParameterSetName="Job")]
[int32]$ThrottleLimit=32
)

<#
 strip off any trailing characters to drive parameter
 that might have been passed.
#>

If ($Drive.Length -gt 2) {
    $Drive=$Drive.Substring(0,2)
}

Write-Verbose "Searching for $Name on Drive $Drive on computer $Computername."

<#
Normally you might think to simply split the name on the . character. But
you might have a filename like myfile.v2.dll so that won't work. In a case
like this the extension would be everything after the last . and the filename
everything before.

So instead I'll use the substring method to "split" the filename string.
#>

#get the index of the last .
$index = $name.LastIndexOf(".")
#get the first part of the name
$filename=$Name.Substring(0,$index)
#get the last part of the name
$extension=$name.Substring($index+1)

#if there is * in the filename or extension, replace it with %
#and change the comparison operator for the WMI query
if ($filename -match "\*" ) {
    Write-Verbose "Wildcard search on filename"
    $filename = $filename.Replace("*","%")
    $fileOp="LIKE"
}
else {
    $fileOp="="
}
if ($extension -match "\*") {
    Write-Verbose "Wildcard search on extension"
    $extension = $extension.Replace("*","%")
    $extOp="LIKE"
}
else {
    $extOp="="
}
$filter = "Filename $fileOp '$filename' AND extension $extOp '$extension' AND Drive='$drive'"
Write-Verbose $filter

#build the core command
$cmd="Get-WmiObject -Class CIM_Datafile -Filter ""$filter"" -ComputerName $Computername"

if ($credential) {
  write-Verbose "Adding credential for $($credential.username)"
  $cmd+=" -credential `$credential"

} #if credential

#get all instances of the file and write the WMI object to the pipeline
if ($AsJob) {
    Write-Verbose "Running query as a job"
     $cmd+=" -Asjob -ThrottleLimit $ThrottleLimit"
}
else {
    #record the start time
    $start=Get-Date    
}

Write-Verbose $cmd
Invoke-Expression -Command $cmd

#display how long this took if not running as a job
if ($start) {
    #get the end time and report how long the search took
    $end=Get-Date
    Write-Verbose "Search completed in $($end-$start)"
}

} #end Get-CIMFile

The last major change is that I construct a Get-WmiObject command string based on the parameter values. I start with a core command.

#build the core command
$cmd="Get-WmiObject -Class CIM_Datafile -Filter ""$filter"" -ComputerName $Computername"

And then add other parameters based on the user’s input. I use Invoke-Expression to run the final command.

You can download my revised version of Get-CIMFile

Finally, while I was working on this revision PowerShell madman Boe Prox was working up his own version which has even more bells and whistles. Many of the articles I write here are intended as tutorials and not necessarily production ready tools. Boe’s version on the other hand is on PEDs (PowerShell Enhancing Drug). Check it out.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati

About the Author

PowerShell.org Announcer

This is the official account for PowerShell.org and sponsor announcements.