Win32_Directory Scripting
In the last article, I introduced you to the CIM_DataFile
CIM class. I demonstrated how you might use it as an alternative to the file system. You can use a CIM session for remoting and get additional properties.
#create CIMSession to a remote computer
[CimSession]$cs = 'thinkx1-jh'
#query properties
$Properties = "Name","FileSize","LastModified","FileType"
$query= "Select $($Properties -join ',') from CIM_DataFile where Name = 'c:\\scripts\\db.png'"
#update properties for Select-Object
$Properties+= @{Name="ComputerName";Expression={$cs.ComputerName.ToUpper()}}
$cs.QueryInstances("Root/Cimv2","WQL",$query) | Select-Object -Property $Properties
Personally, I find the property names a bit more meaningful.
Name : c:\scripts\db.png
FileSize : 13664
LastModified : 7/16/2013 5:42:07 PM
FileType : PNG Image
ComputerName : THINKX1-JH
As I showed last time, you can get all files from a single directory.
$cs.QueryInstances("Root/Cimv2","WQL","Select * from CIM_DataFile where Path = '\\work\\' AND Drive = 'C:' AND FileSize>=$(5MB)") | Select-Object Name,FileSize,CreationDate,LastModified,FileType | Sort-Object FileSize -descending | Format-Table

It is just as easy to get all files from a single location.
[object[]]$a = $cs.QueryInstances("Root/Cimv2","WQL","Select * from CIM_DataFile where Path = '\\scripts\\' AND Drive = 'C:'")
This took 5.2 seconds top get 4166 files remotely. Because of the way the QueryInstance
method works, I'm specifically casting $a
as an array . This make it easier to work with the output.
$a | Group Filetype | Sort-Object count -Descending | Select-Object -first 10 -Property Count,Name,
@{Name='TotalSizeMB';Expression={($_.Group | Measure-Object -Property FileSize -Sum).Sum / 1KB}}

Where this gets trickier is when you want to recurse through a folder. I want to show you some options for that situation today.
Win32_Directory
We will be using the Win32_Directory
class.
Get-CimMember Win32_Directory
I'm using the Get-CimMember function from the PSScriptTools module.

You can filter on Name
property. Don't forget to escape the backslash.
PS C:\> Get-CimInstance win32_directory -Filter "Name='c:\\temp'" | Select *
Status : OK
Compressed : False
Encrypted : False
Name : c:\temp
Readable : True
Writeable : True
Caption : c:\temp
Description : c:\temp
InstallDate : 2/13/2023 5:00:56 PM
AccessMask : 18809343
Archive : False
CompressionMethod :
CreationClassName : CIM_LogicalFile
CreationDate : 2/13/2023 5:00:56 PM
CSCreationClassName : Win32_ComputerSystem
CSName : PROSPERO
Drive : c:
EightDotThreeFileName : c:\temp
EncryptionMethod :
Extension :
FileName : temp
FileSize :
FileType : File Folder
FSCreationClassName : Win32_FileSystem
FSName : NTFS
Hidden : False
InUseCount :
LastAccessed : 6/11/2024 9:32:01 AM
LastModified : 6/3/2024 12:09:59 PM
Path : \
System : False
PSComputerName :
CimClass : root/cimv2:Win32_Directory
CimInstanceProperties : {Caption, Description, InstallDate, Name…}
CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties
And don't include a trailing slash on the path. That will break the query.
PS C:\> Get-CimInstance win32_directory -Filter "Name='c:\\temp\'"
Get-CimInstance: Invalid query
Getting AssociatedInstances
How does this help us get files? We can use the WMI AssociatorsOf
concept. This is a way of searching for related, or associated items. The easiest way is to get a CIM object and then pipe it Get-CimAssociatedInstance
. There may be multiple types of associated objects. I am filtering the results on the CIM_DataFile
class.
$b = Get-CimInstance win32_directory -Filter "Name='c:\\temp'" | Get-CimAssociatedInstance -ResultClassName CIM_DataFile
This took about one second to run and returned 71 files. However, this is only for the root folder C:\temp
. This is not including any sub-folders.
You might think that you can run commands like this and then pipe the output to Get-CimAssociatedInstance
.
Get-CimInstance win32_directory -Filter "Drive='c:' AND Name LIKE 'c:\\temp\\%'"
Get-CimInstance win32_directory -Filter "Drive='c:' AND Caption LIKE 'c:\\temp\\%'"
But these commands will fail., Wildcards won't work in this context.
Getting All Folders
One option is to get associated directories.
$d = Get-CimInstance win32_directory -Filter "Name='c:\\temp'" | Get-CimAssociatedInstance -ResultClassName Win32_Directory
This will return the top-level folders.
PS C:\> $d.Name
c:\temp\briefcase
c:\temp\buttondown
c:\temp\dbox
c:\temp\foo
c:\
The first thing I need to do is filter out the root of C: since my goal is to eventually get all sub-folders under C:\Temp
.
$d = Get-CimInstance win32_directory -Filter "Name='c:\\temp'" |
Get-CimAssociatedInstance -ResultClassName Win32_Directory |
Where-Object { $_.Name -match '^c:\\temp' }
This is closer.
PS C:\> $d.name
c:\temp\briefcase
c:\temp\buttondown
c:\temp\dbox
c:\temp\foo
PS C:\>
Do you see where I'm going with this? I eventually need to get all of these folders.
PS C:\> Show-Tree c:\temp
C:\temp
+--briefcase
| +--docs
| \--ISETabForm
| +--.vs
| | \--ISETabForm
| | \--v16
| | \--Server
| | \--sqlite3
| \--ISETabForm
| +--bin
| | \--Debug
| +--obj
| | \--Debug
| | \--TempPE
| \--Properties
+--buttondown
| \--posts
+--dbox
\--foo
\--bar
\--data
PS C:\>
The
Show-Tree
function is also in the PSScriptTools module.
Recursion Scripting
Since I know how to get parent folders, I can recurse each child folder to get its children. I find recursion easier with a defined PowerShell function.
Function Get-CimFolder {
[cmdletbinding()]
Param([string]$Path)
$Name = $Path.replace('\', '\\')
Get-CimInstance win32_directory -Filter "Name='$Name'" |
Get-CimAssociatedInstance -ResultClassName Win32_Directory |
Where-Object { $_.Name -match "^$Name" } |
ForEach-Object {
#write the parent folder object to the pipeline
$_
#recurse for each child
Get-CimFolder -Path $_.Name
}
}
The path must be a full filesystem path like C:\scripts
.
Get-CimFolder c:\temp | Format-Table Name, Hidden, LastModified -AutoSize

I can combine this technique with what I showed earlier.
$files = Get-CimFolder c:\temp | Foreach-Object {
Write-Host "Getting files for $($_.Name)" -ForegroundColor Yellow
$Name = $_.Name.replace('\', '\\')
Get-CimInstance win32_directory -Filter "Name='$Name'" |
Get-CimAssociatedInstance -ResultClassName CIM_DataFile
}
This finds 694 files, but doesn't include files in the root of C:\Temp
.
$List = [System.Collections.Generic.List[object]]::new()
Get-CimInstance win32_directory -Filter "Name='c:\\temp'" |
Get-CimAssociatedInstance -ResultClassName CIM_DataFile |
Foreach-Object {$List.Add($_)}
Get-CimFolder c:\temp | Foreach-Object {
Write-Host "Getting files for $($_.Name)" -ForegroundColor Yellow
$Name = $_.Name.replace('\', '\\')
Get-CimInstance win32_directory -Filter "Name='$Name'" |
Get-CimAssociatedInstance -ResultClassName CIM_DataFile |
Foreach-Object {$List.Add($_)}
}
Now I have all files under C:\Temp
.
PS C:\> $list.count
765
PS C:\> $list.FindAll({$args.extension -eq 'jpg'}) | select Name,FileSize,LastModified
Name FileSize LastModified
---- -------- ------------
c:\temp\Alien-1979.jpg 17817 1/2/2024 1:45:15 PM
c:\temp\combined.jpg 475090 3/8/2024 11:43:39 AM
c:\temp\Enter the Dragon-1973.jpg 39751 1/2/2024 2:17:54 PM
c:\temp\hagar-foo.jpg 475090 3/8/2024 11:46:54 AM
c:\temp\hagar.jpg 326714 3/8/2024 9:30:23 AM
c:\temp\Hi-And-Lois.jpg 438718 3/8/2024 11:23:31 AM
c:\temp\hi-combined.jpg 584720 3/8/2024 11:45:54 AM
c:\temp\slack-imgs.jpg 82051 1/24/2024 11:24:28 AM
c:\temp\briefcase\Jeff-Hicks-Profile.jpg 4722768 3/25/2015 11:24:45 AM
c:\temp\briefcase\powershell-hero.jpg 196909 7/9/2018 9:08:20 AM
I am not showing it here, but the output also includes hidden files. If I had hidden folders, those too are used by default. I might want to take this into account.
Summary
I think I've given you enough to play with for today. Everything I've shown is proof-of-concept and intended to illustrate concepts and techniques. Ultimately, I want to build my own tooling using the native CIM classes for better performance. Although, there's nothing wrong with building functions that wrap Get-CimInstance
and Get-CimAssociatedInstance
. But I'm betting that at least a few of you want to roll up your sleeves and dive a little deeper. We'll look at what that entails next time.
What are the main advantages of using cim (CIM_DataFile & Win32_Directory) over the standard commands (Get-Item & Get-ChildItem)?
Get-Item and Get-ChildItem work locally, or through a PowerShell remoting session. I'm suing the CIM classes as an alternative to working remotely. I'm not saying it is the best way, just an alternative.