Use Get-CIMInstance to Create a PowerShell File Extension Report

I love seeing what other people are doing with PowerShell. That is why I keep an eye on the #PowerShell hashtag on Twitter. I always find something interesting and useful. Often, I even learn something new. Usually when I find something new and intriguing, I decide to see what I can do with it. I am always interested to see how I can take an example and expand it. Not too long ago I came across a blog post via Twitter that used Get-CIMInstance to retrieve a list of fixed hard drives and then create a file extension report for all files on that drive. I thought it was nicely done and a clever way to combine different commands. It inspired me to see how far I could take these techniques.

Using the Get-CimInstance Cmdlet

Using Get-CimInstance is the modern way of accessing Windows Management Instrumentation (WMI) information. This is especially true because it is easy to query remote computers. One drawback to the original code was that it used Get-ChildItem to enumerate all files on the specified drive. That works fine locally but not remotely. You could wrap the entire command in scriptblock and run it remotely with Invoke-Command. But I thought since we’re using WMI already, why not use WMI to query for all the files? We can accomplish this using the CIM_Datafile class.
Here is a sample that finds all files on my E: drive grouped by the Extension property.

Get-CimInstance -classname CIM_Datafile -filter "drive='E:'" -outvariable e | Group Extension |
Select Name,Count,
@{Name="SizeMB";Expression={[math]::Round(($_.Group | Measure-Object -property filesize -Sum).Sum/1MB,4)}} |
Sort Count -Descending

The output is similar to the inspiring article where I include a custom property to measure all of the files for each extension and format the result in MB to four decimal places. Note that the property I’m measuring is Filesize and not length, because I have CIM_Datefile objects, not System.IO.FileInfo. Here’s a sample instance.

Status                : OK
Name                  : e:\application\mcafee\factory\apps\msc\factory\msc_x86_5_1_1028_au.cab
Caption               : e:\application\mcafee\factory\apps\msc\factory\msc_x86_5_1_1028_au.cab
Description           : e:\application\mcafee\factory\apps\msc\factory\msc_x86_5_1_1028_au.cab
InstallDate           : 1/23/2014 1:55:52 AM
AccessMask            : 18809343
Archive               : True
Compressed            : False
CompressionMethod     :
CreationClassName     : CIM_LogicalFile
CreationDate          : 1/23/2014 1:55:52 AM
CSCreationClassName   : Win32_ComputerSystem
CSName                : WIN81-ENT-01
Drive                 : e:
EightDotThreeFileName : e:\application\mcafee\factory\apps\msc\factory\msc_x86_5_1_1028_au.cab
Encrypted             : False
EncryptionMethod      :
Extension             : cab
FileName              : msc_x86_5_1_1028_au
FileSize              : 4319
FileType              : WinRAR archive
FSCreationClassName   : Win32_FileSystem
FSName                : NTFS
Hidden                : False
InUseCount            :
LastAccessed          : 1/23/2014 1:55:52 AM
LastModified          : 8/9/2013 2:56:56 PM
Path                  : \application\mcafee\factory\apps\msc\factory\
Readable              : True
System                : False
Writeable             : True
Manufacturer          :
Version               :
PSComputerName        :
CimClass              : root/cimv2:CIM_DataFile
CimInstanceProperties : {Caption, Description, InstallDate, Name...}
CimSystemProperties   : Microsoft.Management.Infrastructure.CimSystemProperties

Given all of that, here’s my result.

Results of Get-CIMInstance in Windows PowerShell. (Image Credit: Jeff Hicks)
Results of Get-CIMInstance in Windows PowerShell. (Image Credit: Jeff Hicks)

I saved the output of the Get-CimInstance expression to a variable $e by using the common Out-Variable parameter. It has an alias of ov.

This way I can re-use the results without having to re-run a potentially long command. Let’s say I want to find the top ten most used extensions.

$e | Group FileType | Select Name,Count,
@{Name="SizeMB";Expression={[math]::Round(($_.Group | Measure-Object -property filesize -Sum).Sum/1MB,4)}} |
Sort Count -Descending | Out-GridView -title "Drive E: File Types"

Top 10 most used extensions. (Image Credit: Jeff Hicks)
Top 10 most used extensions. (Image Credit: Jeff Hicks)

Or I can group by something else. For example, I see a property called FileType. Let’s group and measure results based on that property.

$e | Group FileType | Select Name,Count,
@{Name="SizeMB";Expression={[math]::Round(($_.Group | Measure-Object -property filesize -Sum).Sum/1MB,4)}} |
Sort Count -Descending | Out-GridView -title "Drive E: File Types"

Grouping and measuring results using the FileType property. (Image Credit: Jeff Hicks)
Grouping and measuring results using the FileType property. (Image Credit: Jeff Hicks)

Maybe that is more meaningful to you. If you try this, then you might notice that the query includes hidden and system files. I probably don’t need to count them so I can filter them out.

Get-CimInstance -classname CIM_Datafile -filter "drive='E:' AND System = 'False' AND Hidden = 'False'"

Note that the values are strings and not the PowerShell boolean values. Now that I have some code that works for a single drive, let’s create a report for all fixed drives.

(Get-CimInstance -ClassName Win32_logicaldisk -Filter "DriveType=3").DeviceID | foreach {
  Get-CimInstance -ClassName CIM_Datafile -filter "drive='$_' AND System = 'False' AND Hidden = 'False'" -ov $_[0]|
  Group Extension |  Select Name,Count,
  @{Name="SizeMB";Expression={[math]::Round(($_.Group | Measure-Object -property filesize -Sum).Sum/1MB,4)}} |
  Sort Count -Descending
} | out-file c:\work\filereport.txt

I’m running this locally for now and saving the results to a text file, but you could easily export to a CSV file or anything else you can think of. This could take some time to run in which case you could always wrap the command in a scriptblock and use Start-Job.
The benefit of using Get-CimInstance is that I can use this command to query a remote computer.

$cs = New-CimSession -computername CHI-Win81 -credential globomantics\administrator
Get-CimInstance -classname CIM_Datafile -filter "drive='C:' AND System = 'False' AND Hidden = 'False'" -ov c -cimsession $cs |
Group Extension | Select Name,Count,
@{Name="SizeMB";Expression={[math]::Round(($_.Group | Measure-Object -property filesize -Sum).Sum/1MB,4)}} |
Sort Count -Descending

Although, all of the processing of grouping and selecting is happening on my computer after I retrieve the results. Depending on the situation it might make more sense to have the remote computer do the work. Especially if I am querying multiple machines.

In this situation, since I know the remote computer supports PowerShell remoting, I can use a PSSession. I’ll define a scriptblock to only search the C: drive for files.

$sb = {
Get-CimInstance -classname CIM_Datafile -filter "drive='C:' AND System = 'False' AND Hidden = 'False'" |
Group Extension | Select Name,Count,
@{Name="SizeMB";Expression={[math]::Round(($_.Group | Measure-Object -property filesize -Sum).Sum/1MB,4)}} |
Sort Count -Descending
}

Next I can create some PSSessions, or I could just use computernames.

$sess = new-pssession -computername chi-win81,chi-fp02 -Credential globomantics\administrator

Finally, I’ll use Invoke-Command, and I’ll execute it as a background job because this could take a while to run.

Invoke-Command -scriptblock $sb -session $sess -asjob -JobName FileReport

When the job is complete I can receive the results.

$data = Receive-Job -Name FileReport -Keep

I can format the results anyway I need:

$data | sort PSComputername,Count,Name -Descending | format-Table -GroupBy PSComputername -Property Name,Count,SizeMB

I’m not implying that any of these examples are the best or only way to accomplish this task. But I think it is important that you experiment with different techniques and use Measure-Command to help identify which performs the best in our environment.
If you get stuck with any of my examples, please post a problem in the PowerShell forum on the site. I also hope you’ll keep an eye out on Twitter and other platforms for interesting PowerShell tidbits that you can take and re-purpose into something that makes your job easier.