Last 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.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
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.