Finding files is one of those necessary evils for IT Pros. Sometimes we're searching for a needle in a haystack. And it gets even more complicated when the haystacks are on 10 or 100 or 1000 remote computers. You might think using Get-ChildItem is your only option. Certainly it works, but if you are searching an entire hard drive it is pretty resource intensive.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Another option is to use WMI and CIM_Datafile class. Don't let the name fool you. You can use Get-WmiObject in PowerShell 2.0 or 3.0. Every file, as far as I know, is also registered with WMI so all you need to do is query for all instances of the CIM_Datafile class. However, you must be clever. Just like searching an entire drive, searching via WMI can be time consuming. So you need to make your WMI query as specific as possible. To do that you need to know the properties. Here's what a CIM_Datafile object looks like.
Status : OK
Name : c:\program files (x86)\windows defender\mpclient.dll
__GENUS : 2
__CLASS : CIM_DataFile
__SUPERCLASS : CIM_LogicalFile
__DYNASTY : CIM_ManagedSystemElement
__RELPATH : CIM_DataFile.Name="c:\\program files (x86)\\windows defender\\mpclient.dll"
__PROPERTY_COUNT : 33
__DERIVATION : {CIM_LogicalFile, CIM_LogicalElement, CIM_ManagedSystemElement}
__SERVER : SERENITY
__NAMESPACE : root\cimv2
__PATH : \\SERENITY\root\cimv2:CIM_DataFile.Name="c:\\program files (x86)\\windows d
efender\\mpclient.dll"
AccessMask : 17957033
Archive : True
Caption : c:\program files (x86)\windows defender\mpclient.dll
Compressed : False
CompressionMethod :
CreationClassName : CIM_LogicalFile
CreationDate : 20120725214205.814611-240
CSCreationClassName : Win32_ComputerSystem
CSName : SERENITY
Description : c:\program files (x86)\windows defender\mpclient.dll
Drive : c:
EightDotThreeFileName : c:\program files (x86)\windows defender\mpclient.dll
Encrypted : False
EncryptionMethod :
Extension : dll
FileName : mpclient
FileSize : 662016
FileType : Application Extension
FSCreationClassName : Win32_FileSystem
FSName : NTFS
Hidden : False
InstallDate : 20120725214205.814611-240
InUseCount :
LastAccessed : 20120725214205.814611-240
LastModified : 20120725231905.556000-240
Manufacturer : Microsoft Corporation
Path : \program files (x86)\windows defender\
Readable : True
System : False
Version : 4.0.9200.16384
Writeable : True
Scope : System.Management.ManagementScope
Options : System.Management.ObjectGetOptions
ClassPath : \\SERENITY\root\cimv2:CIM_DataFile
Properties : {AccessMask, Archive, Caption, Compressed...}
SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...}
Qualifiers : {dynamic, Locale, provider, UUID}
Site :
Container :
At a minimum you should limit your query to the drive. Otherwise the WMI query will search ALL drives. If you are searching by path, description or caption, don't forget that the \ character needs to be escaped, e.g. \\program files (x86)\\windows defender\\. Of course if you know that much already you might as well use Get-Childitem.
For me, the real benefit in using WMI is when I know the file name but don't know for sure where it might be on a given drive. So I put together an advanced function called Get-CIMFile.
Function Get-CIMFile {
#comment based help is here
[cmdletbinding()]
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,
[switch]$AsJob
)
<#
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)
$filter = "Filename='$filename' AND extension='$extension' AND Drive='$drive'"
Write-Verbose $filter
#get all instances of the file and write the WMI object to the pipeline
Get-WmiObject -Class CIM_Datafile -Filter $filter -ComputerName $Computername -Asjob:$AsJob
} #end Get-CIMFile
The code is documented to explain what is going on so I won't repeat it here. The function will work anywhere you have WMI access. This version doesn't handle alternate credentials or other features of Get-WmiObject, which you could add if you want. But with this I can check files on multiple computers. Suppose I'm concerned about a vulnerability like the recent Java problem. Or I need to see if any computers are out of date on a given file. I can run a command like this.
PS Scripts:\> $files = get-cimfile mpclient.dll -comp serenity,novo8
PS Scripts:\> $files | Sort name,CSName | Select Name,Version,CSName
Name Version CSName
---- ------- ------
c:\program files (x86)\windows... 4.0.9200.16384 SERENITY
c:\program files\windows defen... 4.0.9200.16384 NOVO8
c:\program files\windows defen... 4.0.9200.16384 SERENITY
c:\windows\winsxs\amd64_window... 4.0.9200.16384 SERENITY
c:\windows\winsxs\wow64_window... 4.0.9200.16384 SERENITY
c:\windows\winsxs\x86_windows-... 4.0.9200.16384 NOVO8
The command writes the full WMI object to the pipeline so I could filter or format $files however I need.
Download Get-CIMFile and let me know what you think.
Jeff,
Nice. The capability of handling wildcards in the file name would greatly enhance the usefulness.
There are any number of enhancements that could be made. But then you would have nothing to do! Still, I’ll see what other feedback I get. Thanks for taking the time to respond.
Great stuff as always, Jeff! I had some free time and decided to take on the challenge of adding some more things to the this. 🙂 Went ahead and added alt. credential support, wildcard support and specifying multiple files. Of course working with wildcards causes the function to take a performance hit, but it is there anyways. I’ll try to throw it up on my blog later tonight.
As promised, my blog post with the additions to the function.
http://learn-powershell.net/2013/02/01/use-powershell-and-wmi-to-locate-multiple-files-on-any-drive-in-your-domain/
Great script! I’m trying to find the best way to call a server list for the script to process. That way I could automate it to generate the Excel file with a dated filename and place a copy on a Sharepoint server.
Any recommendations?
You should be able to do something like: get-cimfile mpclient.dll -comp (get-content computers.txt)
Stupid question…..I would like to try to your script out but what do you mean by download it? Should I change the filename to .ps1 and run it? Where should I place it? Are there additional modules to import?
Sorry.
I have been trying to find help with using powershell to check fileversions on multiple computers and your script seems that it will work.
Thanks,
Rob
Download the file and save it with a .ps1 extension. Then dot source the script to load the function.
PS C:\> .
Now you can run the function.
PS C:\> get-cimfile file.dll -comp $computers
The function has help so you can always run: help get-cimfile -full. I suggest you test locally first.
Sorry…….This is what I am getting…
C:\Users\tadmin>.c:\Load_Stuff\get-cimfile.ps1
The filename, directory name, or volume label syntax is incorrect.
It is hard to tell for sure, but to dot source the script, at the prompt you need to type a period then a space then the path to the script file. I’m assuming your execution policy allows you to run scripts and that you have unblocked the file. But you’ll find out soon enough if those are issues.